diff options
Diffstat (limited to 'sources/shiboken6/ApiExtractor')
222 files changed, 45573 insertions, 0 deletions
diff --git a/sources/shiboken6/ApiExtractor/AUTHORS b/sources/shiboken6/ApiExtractor/AUTHORS new file mode 100644 index 000000000..6e802fb53 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/AUTHORS @@ -0,0 +1,8 @@ +Anderson Lizardo <anderson.lizardo@openbossa.org> +Bruno Araujo <bruno.araujo@openbossa.org> +Hugo Parente Lima <hugo.lima@openbossa.org> +Lauro Moura <lauro.neto@openbossa.org> +Luciano Wolf <luciano.wolf@openbossa.org> +Marcelo Lira <marcelo.lira@openbossa.org> +Renato Araujo Oliveira Filho <renato.filho@openbossa.org> + diff --git a/sources/shiboken6/ApiExtractor/CMakeLists.txt b/sources/shiboken6/ApiExtractor/CMakeLists.txt new file mode 100644 index 000000000..7aa2fbd11 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/CMakeLists.txt @@ -0,0 +1,138 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +project(apiextractor) + +cmake_minimum_required(VERSION 3.18) +cmake_policy(VERSION 3.18) + +set(CMAKE_AUTOMOC ON) + +set(apiextractor_SRC +abstractmetaargument.cpp abstractmetaargument.h +abstractmetabuilder.cpp abstractmetabuilder.h abstractmetabuilder_p.h +abstractmetabuilder_helpers.cpp +abstractmetaenum.cpp abstractmetaenum.h +abstractmetafield.cpp abstractmetafield.h +abstractmetafunction.cpp abstractmetafunction.h +abstractmetalang.cpp abstractmetalang.h abstractmetalang_helpers.h abstractmetalang_typedefs.h +abstractmetatype.cpp abstractmetatype.h +addedfunction.cpp addedfunction.h addedfunction_p.h +anystringview_helpers.cpp anystringview_helpers.h +apiextractor.cpp apiextractor.h apiextractorflags.h +apiextractorresult.cpp apiextractorresult.h +arraytypeentry.h +classdocumentation.cpp classdocumentation.h +codesnip.cpp codesnip.h +codesniphelpers.cpp codesniphelpers.h +complextypeentry.h +conditionalstreamreader.cpp conditionalstreamreader.h +configurabletypeentry.h +constantvaluetypeentry.h +containertypeentry.h +customconversion.cpp customconversion.h customconversion_typedefs.h +customtypenentry.h +debughelpers_p.h +dependency.h +documentation.cpp documentation.h +dotview.cpp dotview.h +enclosingclassmixin.cpp enclosingclassmixin.h +enumtypeentry.h +enumvaluetypeentry.h +exception.h +fileout.cpp fileout.h +flagstypeentry.h +functiontypeentry.h +graph.h +header_paths.h +include.cpp include.h +messages.cpp messages.h +modifications.cpp modifications.h modifications_typedefs.h +namespacetypeentry.h +objecttypeentry.h +optionsparser.cpp optionsparser.h +predefined_templates.cpp predefined_templates.h +primitivetypeentry.h +propertyspec.cpp propertyspec.h +pymethoddefentry.cpp pymethoddefentry.h +pythontypeentry.h +reporthandler.cpp reporthandler.h +smartpointertypeentry.h +sourcelocation.cpp sourcelocation.h +templateargumententry.h +textstream.cpp textstream.h +typedatabase.cpp typedatabase.h typedatabase_p.h typedatabase_typedefs.h +typedefentry.h +typeparser.cpp typeparser.h +typesystem.cpp typesystem.h typesystem_enums.h typesystem_typedefs.h +typesystemparser.cpp typesystemparser_p.h +usingmember.h +valuetypeentry.h +varargstypeentry.h +voidtypeentry.h +xmlutils.cpp xmlutils.h xmlutils_libxslt.h xmlutils_qt.h +# Clang +clangparser/clangbuilder.cpp clangparser/clangbuilder.h +clangparser/clangdebugutils.cpp clangparser/clangdebugutils.h +clangparser/clangparser.cpp clangparser/clangparser.h +clangparser/clangutils.cpp clangparser/clangutils.h +clangparser/compilersupport.cpp clangparser/compilersupport.h +# Old parser +parser/codemodel.cpp parser/codemodel.h parser/codemodel_fwd.h parser/codemodel_enums.h +parser/enumvalue.cpp parser/enumvalue.h +parser/typeinfo.cpp parser/typeinfo.h +) + +find_package(LibXml2 2.6.32) +find_package(LibXslt 1.1.19) + +set(HAS_LIBXSLT 0) +if (LIBXSLT_FOUND AND LIBXML2_FOUND) + set(HAS_LIBXSLT 1) +endif() + +if(NOT HAS_LIBXSLT) + set(DISABLE_DOCSTRINGS TRUE) + message(WARNING + "Documentation will not be built due to missing dependency (libxslt not found).") +endif() + +# Export to parent scope so that generator/CMakeLists.txt gets it +set(DISABLE_DOCSTRINGS ${DISABLE_DOCSTRINGS} PARENT_SCOPE) + +add_library(apiextractor STATIC ${apiextractor_SRC}) +target_include_directories(apiextractor PRIVATE ${CLANG_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/parser) +target_link_libraries(apiextractor PUBLIC Qt::Core) +target_link_libraries(apiextractor PRIVATE libclang) + +if (HAS_LIBXSLT) + target_compile_definitions(apiextractor PUBLIC HAVE_LIBXSLT) + target_sources(apiextractor PRIVATE xmlutils_libxslt.cpp) + target_include_directories(apiextractor + PRIVATE ${LIBXSLT_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR}) + target_link_libraries(apiextractor + PRIVATE ${LIBXSLT_LIBRARIES} ${LIBXML2_LIBRARIES}) +endif() + +if (NOT DISABLE_DOCSTRINGS) + target_sources(apiextractor PRIVATE + docparser.cpp docparser.h + doxygenparser.cpp doxygenparser.h + qtdocparser.cpp qtdocparser.h) +endif() + +target_compile_definitions(apiextractor + PRIVATE CMAKE_CXX_COMPILER="${CMAKE_CXX_COMPILER}" + PRIVATE QT_LEAN_HEADERS=1) + +set(LIB_INSTALL_DIR "lib${LIB_SUFFIX}" CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is /lib${LIB_SUFFIX})" FORCE) + +if (BUILD_TESTS) + find_package(Qt6 REQUIRED COMPONENTS Test) + set(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/tests) + enable_testing() + add_subdirectory(tests) +endif() diff --git a/sources/shiboken6/ApiExtractor/COPYING b/sources/shiboken6/ApiExtractor/COPYING new file mode 100644 index 000000000..4ccd71466 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/COPYING @@ -0,0 +1,342 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + 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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +------------------------------------------------------------------------- diff --git a/sources/shiboken6/ApiExtractor/abstractmetaargument.cpp b/sources/shiboken6/ApiExtractor/abstractmetaargument.cpp new file mode 100644 index 000000000..05cebe10a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetaargument.cpp @@ -0,0 +1,201 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetaargument.h" +#include "abstractmetatype.h" +#include "documentation.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QSharedData> + +using namespace Qt::StringLiterals; + +class AbstractMetaArgumentData : public QSharedData +{ +public: + QString toString() const; + + QString m_name; + AbstractMetaType m_type; + AbstractMetaType m_modifiedType; + bool m_hasName = false; + Documentation m_doc; + QString m_expression; + QString m_originalExpression; + int m_argumentIndex = 0; + bool m_modifiedRemoved = false; +}; + +AbstractMetaArgument::AbstractMetaArgument() : d(new AbstractMetaArgumentData) +{ +} + +AbstractMetaArgument::~AbstractMetaArgument() = default; + +AbstractMetaArgument::AbstractMetaArgument(const AbstractMetaArgument &) = default; + +AbstractMetaArgument &AbstractMetaArgument::operator=(const AbstractMetaArgument &) = default; + +AbstractMetaArgument::AbstractMetaArgument(AbstractMetaArgument &&) noexcept = default; + +AbstractMetaArgument &AbstractMetaArgument::operator=(AbstractMetaArgument &&) noexcept = default; + +const AbstractMetaType &AbstractMetaArgument::type() const +{ + return d->m_type; +} + +void AbstractMetaArgument::setType(const AbstractMetaType &type) +{ + if (d->m_type != type) + d->m_type = d->m_modifiedType = type; +} + +const AbstractMetaType &AbstractMetaArgument::modifiedType() const +{ + return d->m_modifiedType; +} + +bool AbstractMetaArgument::isTypeModified() const +{ + return modifiedType() != type(); +} + +bool AbstractMetaArgument::isModifiedRemoved() const +{ + return d->m_modifiedRemoved; +} + +void AbstractMetaArgument::setModifiedRemoved(bool v) +{ + if (d->m_modifiedRemoved != v) + d->m_modifiedRemoved = v; +} + +void AbstractMetaArgument::setModifiedType(const AbstractMetaType &type) +{ + if (d->m_modifiedType != type) + d->m_modifiedType = type; +} + +QString AbstractMetaArgument::name() const +{ + return d->m_name; +} + +void AbstractMetaArgument::setName(const QString &name, bool realName) +{ + if (d->m_name != name || d->m_hasName != realName) { + d->m_name = name; + d->m_hasName = realName; + } +} + +bool AbstractMetaArgument::hasName() const +{ + return d->m_hasName; +} + +void AbstractMetaArgument::setDocumentation(const Documentation &doc) +{ + if (d->m_doc != doc) + d->m_doc = doc; +} + +Documentation AbstractMetaArgument::documentation() const +{ + return d->m_doc; +} + +QString AbstractMetaArgument::defaultValueExpression() const +{ + return d->m_expression; +} + +void AbstractMetaArgument::setDefaultValueExpression(const QString &expr) +{ + if (d->m_expression != expr) + d->m_expression = expr; +} + +QString AbstractMetaArgument::originalDefaultValueExpression() const +{ + return d->m_originalExpression; +} + +void AbstractMetaArgument::setOriginalDefaultValueExpression(const QString &expr) +{ + if (d->m_originalExpression != expr) + d->m_originalExpression = expr; +} + +bool AbstractMetaArgument::hasOriginalDefaultValueExpression() const +{ + return !d->m_originalExpression.isEmpty(); +} + +bool AbstractMetaArgument::hasDefaultValueExpression() const +{ + return !d->m_expression.isEmpty(); +} + +bool AbstractMetaArgument::hasUnmodifiedDefaultValueExpression() const +{ + return !d->m_originalExpression.isEmpty() && d->m_originalExpression == d->m_expression; +} + +bool AbstractMetaArgument::hasModifiedDefaultValueExpression() const +{ + return !d->m_expression.isEmpty() && d->m_originalExpression != d->m_expression; +} + +QString AbstractMetaArgumentData::toString() const +{ + QString result = m_type.name() + u' ' + m_name; + if (!m_expression.isEmpty()) + result += u" = "_s + m_expression; + return result; +} + +QString AbstractMetaArgument::toString() const +{ + return d->toString(); +} + +int AbstractMetaArgument::argumentIndex() const +{ + return d->m_argumentIndex; +} + +void AbstractMetaArgument::setArgumentIndex(int argIndex) +{ + if (d->m_argumentIndex != argIndex) + d->m_argumentIndex = argIndex; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const AbstractMetaArgument &aa) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaArgument(" << aa.toString() << ')'; + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaArgument *aa) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaArgument("; + if (aa != nullptr) + d << aa->toString(); + else + d << '0'; + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetaargument.h b/sources/shiboken6/ApiExtractor/abstractmetaargument.h new file mode 100644 index 000000000..38402e369 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetaargument.h @@ -0,0 +1,66 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETAARGUMENT_H +#define ABSTRACTMETAARGUMENT_H + +#include <QtCore/QSharedDataPointer> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class AbstractMetaType; +class AbstractMetaArgumentData; +class Documentation; + +class AbstractMetaArgument +{ +public: + AbstractMetaArgument(); + ~AbstractMetaArgument(); + AbstractMetaArgument(const AbstractMetaArgument &); + AbstractMetaArgument &operator=(const AbstractMetaArgument &); + AbstractMetaArgument(AbstractMetaArgument &&) noexcept; + AbstractMetaArgument &operator=(AbstractMetaArgument &&) noexcept; + + const AbstractMetaType &type() const; + void setType(const AbstractMetaType &type); + void setModifiedType(const AbstractMetaType &type); + const AbstractMetaType &modifiedType() const; + bool isTypeModified() const; + + bool isModifiedRemoved() const; + void setModifiedRemoved(bool v); + + QString name() const; + void setName(const QString &name, bool realName = true); + bool hasName() const; + + void setDocumentation(const Documentation& doc); + Documentation documentation() const; + + QString defaultValueExpression() const; + void setDefaultValueExpression(const QString &expr); + + QString originalDefaultValueExpression() const; + void setOriginalDefaultValueExpression(const QString &expr); + + bool hasDefaultValueExpression() const; + bool hasOriginalDefaultValueExpression() const; + bool hasUnmodifiedDefaultValueExpression() const; + bool hasModifiedDefaultValueExpression() const; + + QString toString() const; + + int argumentIndex() const; + void setArgumentIndex(int argIndex); + +private: + QSharedDataPointer<AbstractMetaArgumentData> d; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const AbstractMetaArgument &aa); +QDebug operator<<(QDebug d, const AbstractMetaArgument *aa); +#endif + +#endif // ABSTRACTMETAARGUMENT_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp new file mode 100644 index 000000000..fa0767a62 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.cpp @@ -0,0 +1,3749 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetabuilder_p.h" +#include "abstractmetaargument.h" +#include "abstractmetaenum.h" +#include "abstractmetafield.h" +#include "abstractmetafunction.h" +#include "abstractmetatype.h" +#include "addedfunction.h" +#include "graph.h" +#include "debughelpers_p.h" +#include "exception.h" +#include "messages.h" +#include "propertyspec.h" +#include "reporthandler.h" +#include "sourcelocation.h" +#include "typedatabase.h" +#include "enumtypeentry.h" +#include "enumvaluetypeentry.h" +#include "arraytypeentry.h" +#include "constantvaluetypeentry.h" +#include "containertypeentry.h" +#include "flagstypeentry.h" +#include "functiontypeentry.h" +#include "namespacetypeentry.h" +#include "primitivetypeentry.h" +#include "smartpointertypeentry.h" +#include "templateargumententry.h" +#include "typedefentry.h" +#include "typesystemtypeentry.h" +#include "usingmember.h" + +#include "parser/codemodel.h" + +#include <clangparser/clangbuilder.h> +#include <clangparser/clangutils.h> +#include <clangparser/compilersupport.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QMetaObject> +#include <QtCore/QQueue> +#include <QtCore/QRegularExpression> +#include <QtCore/QTemporaryFile> +#include <QtCore/QTextStream> + +#include <cstdio> +#include <algorithm> +#include <memory> + +using namespace Qt::StringLiterals; + +static QString stripTemplateArgs(const QString &name) +{ + const auto pos = name.indexOf(u'<'); + return pos < 0 ? name : name.left(pos); +} + +static void fixArgumentIndexes(AbstractMetaArgumentList *list) +{ + for (qsizetype i = 0, size = list->size(); i < size; ++i) + (*list)[i].setArgumentIndex(i); +} + +bool operator<(const RejectEntry &re1, const RejectEntry &re2) +{ + return re1.reason != re2.reason + ? (re1.reason < re2.reason) : (re1.sortkey < re2.sortkey); +} + +QTextStream &operator<<(QTextStream &str, const RejectEntry &re) +{ + str << re.signature; + if (!re.message.isEmpty()) + str << ": " << re.message; + return str; +} + +static void applyCachedFunctionModifications(AbstractMetaFunction *metaFunction, + const FunctionModificationList &functionMods) +{ + for (const FunctionModification &mod : functionMods) { + if (mod.exceptionHandling() != TypeSystem::ExceptionHandling::Unspecified) + metaFunction->setExceptionHandlingModification(mod.exceptionHandling()); + if (mod.allowThread() != TypeSystem::AllowThread::Unspecified) + metaFunction->setAllowThreadModification(mod.allowThread()); + } +} + +bool AbstractMetaBuilderPrivate::m_useGlobalHeader = false; +bool AbstractMetaBuilderPrivate::m_codeModelTestMode = false; + +AbstractMetaBuilderPrivate::AbstractMetaBuilderPrivate() : + m_logDirectory(u"."_s + QDir::separator()) +{ +} + +AbstractMetaBuilder::AbstractMetaBuilder() : d(new AbstractMetaBuilderPrivate) +{ + d->q = this; +} + +AbstractMetaBuilder::~AbstractMetaBuilder() +{ + delete d; +} + +const AbstractMetaClassList &AbstractMetaBuilder::classes() const +{ + return d->m_metaClasses; +} + +AbstractMetaClassList AbstractMetaBuilder::takeClasses() +{ + AbstractMetaClassList result; + qSwap(result, d->m_metaClasses); + return result; +} + +const AbstractMetaClassList &AbstractMetaBuilder::templates() const +{ + return d->m_templates; +} + +AbstractMetaClassList AbstractMetaBuilder::takeTemplates() +{ + AbstractMetaClassList result; + qSwap(result, d->m_templates); + return result; +} + +const AbstractMetaClassList &AbstractMetaBuilder::smartPointers() const +{ + return d->m_smartPointers; +} + +AbstractMetaClassList AbstractMetaBuilder::takeSmartPointers() +{ + AbstractMetaClassList result; + qSwap(result, d->m_smartPointers); + return result; +} + +const AbstractMetaFunctionCList &AbstractMetaBuilder::globalFunctions() const +{ + return d->m_globalFunctions; +} + +const AbstractMetaEnumList &AbstractMetaBuilder::globalEnums() const +{ + return d->m_globalEnums; +} + +const QHash<TypeEntryCPtr, AbstractMetaEnum> &AbstractMetaBuilder::typeEntryToEnumsHash() const +{ + return d->m_enums; +} + +const QMultiHash<QString, QString> &AbstractMetaBuilder::typedefTargetToName() const +{ + return d->m_typedefTargetToName; +} + +void AbstractMetaBuilderPrivate::checkFunctionModifications() const +{ + const auto &entries = TypeDatabase::instance()->entries(); + + for (auto it = entries.cbegin(), end = entries.cend(); it != end; ++it) { + TypeEntryCPtr entry = it.value(); + if (!entry) + continue; + if (!entry->isComplex() || !entry->generateCode()) + continue; + + auto centry = std::static_pointer_cast<const ComplexTypeEntry>(entry); + + if (!centry->generateCode()) + continue; + + FunctionModificationList modifications = centry->functionModifications(); + + for (const FunctionModification &modification : std::as_const(modifications)) { + QString signature = modification.signature(); + + QString name = signature.trimmed(); + name.truncate(name.indexOf(u'(')); + + const auto clazz = AbstractMetaClass::findClass(m_metaClasses, centry); + if (!clazz) + continue; + + bool found = false; + QStringList possibleSignatures; + for (const auto &function : clazz->functions()) { + if (function->implementingClass() == clazz + && modification.matches(function->modificationSignatures())) { + found = true; + break; + } + + if (function->originalName() == name) { + const QString signatures = function->modificationSignatures().join(u'/'); + possibleSignatures.append(signatures + u" in "_s + + function->implementingClass()->name()); + } + } + + if (!found) { + qCWarning(lcShiboken).noquote().nospace() + << msgNoFunctionForModification(clazz, signature, + modification.originalSignature(), + possibleSignatures, clazz->functions()); + } + } + } +} + +AbstractMetaClassPtr AbstractMetaBuilderPrivate::argumentToClass(const ArgumentModelItem &argument, + const AbstractMetaClassCPtr ¤tClass) +{ + AbstractMetaClassPtr returned; + auto type = translateType(argument->type(), currentClass); + if (!type.has_value()) + return returned; + TypeEntryCPtr entry = type->typeEntry(); + if (entry && entry->isComplex()) + returned = AbstractMetaClass::findClass(m_metaClasses, entry); + return returned; +} + +/** + * Checks the argument of a hash function and flags the type if it is a complex type + */ +void AbstractMetaBuilderPrivate::registerHashFunction(const FunctionModelItem &function_item, + const AbstractMetaClassPtr ¤tClass) +{ + if (function_item->isDeleted()) + return; + ArgumentList arguments = function_item->arguments(); + if (arguments.size() >= 1) { // (Class, Hash seed). + if (AbstractMetaClassPtr cls = argumentToClass(arguments.at(0), currentClass)) + cls->setHashFunction(function_item->name()); + } +} + +void AbstractMetaBuilderPrivate::registerToStringCapabilityIn(const NamespaceModelItem &nsItem) +{ + const FunctionList &streamOps = nsItem->findFunctions("operator<<"); + for (const FunctionModelItem &item : streamOps) + registerToStringCapability(item, nullptr); + for (const NamespaceModelItem &ni : nsItem->namespaces()) + registerToStringCapabilityIn(ni); +} + +/** + * Check if a class has a debug stream operator that can be used as toString + */ + +void AbstractMetaBuilderPrivate::registerToStringCapability(const FunctionModelItem &function_item, + const AbstractMetaClassPtr ¤tClass) +{ + ArgumentList arguments = function_item->arguments(); + if (arguments.size() == 2) { + if (arguments.at(0)->type().toString() == u"QDebug") { + const ArgumentModelItem &arg = arguments.at(1); + if (AbstractMetaClassPtr cls = argumentToClass(arg, currentClass)) { + if (arg->type().indirections() < 2) + cls->setToStringCapability(true, int(arg->type().indirections())); + } + } + } +} + +void AbstractMetaBuilderPrivate::traverseOperatorFunction(const FunctionModelItem &item, + const AbstractMetaClassPtr ¤tClass) +{ + if (item->accessPolicy() != Access::Public) + return; + + const ArgumentList &itemArguments = item->arguments(); + bool firstArgumentIsSelf = true; + bool unaryOperator = false; + + auto baseoperandClass = argumentToClass(itemArguments.at(0), currentClass); + + if (itemArguments.size() == 1) { + unaryOperator = true; + } else if (!baseoperandClass + || !baseoperandClass->typeEntry()->generateCode()) { + baseoperandClass = argumentToClass(itemArguments.at(1), currentClass); + firstArgumentIsSelf = false; + } else { + auto type = translateType(item->type(), currentClass); + const auto retType = type.has_value() ? type->typeEntry() : TypeEntryCPtr{}; + const auto otherArgClass = argumentToClass(itemArguments.at(1), currentClass); + if (otherArgClass && retType + && (retType->isValue() || retType->isObject()) + && retType != baseoperandClass->typeEntry() + && retType == otherArgClass->typeEntry()) { + baseoperandClass = AbstractMetaClass::findClass(m_metaClasses, retType); + firstArgumentIsSelf = false; + } + } + if (!baseoperandClass) { + rejectFunction(item, currentClass, AbstractMetaBuilder::UnmatchedOperator, + u"base operand class not found."_s); + return; + } + + if (item->isSpaceshipOperator() && !item->isDeleted()) { + AbstractMetaClass::addSynthesizedComparisonOperators(baseoperandClass); + return; + } + + AbstractMetaFunction *metaFunction = traverseFunction(item, baseoperandClass); + if (metaFunction == nullptr) + return; + + auto flags = metaFunction->flags(); + // Strip away first argument, since that is the containing object + AbstractMetaArgumentList arguments = metaFunction->arguments(); + if (firstArgumentIsSelf || unaryOperator) { + AbstractMetaArgument first = arguments.takeFirst(); + fixArgumentIndexes(&arguments); + if (!unaryOperator && first.type().indirections()) + metaFunction->setPointerOperator(true); + metaFunction->setArguments(arguments); + flags.setFlag(AbstractMetaFunction::Flag::OperatorLeadingClassArgumentRemoved); + if (first.type().passByValue()) + flags.setFlag(AbstractMetaFunction::Flag::OperatorClassArgumentByValue); + } else { + // If the operator method is not unary and the first operator is + // not of the same type of its owning class we suppose that it + // must be an reverse operator (e.g. CLASS::operator(TYPE, CLASS)). + // All operator overloads that operate over a class are already + // being added as member functions of that class by the API Extractor. + AbstractMetaArgument last = arguments.takeLast(); + if (last.type().indirections()) + metaFunction->setPointerOperator(true); + metaFunction->setArguments(arguments); + metaFunction->setReverseOperator(true); + flags.setFlag(AbstractMetaFunction::Flag::OperatorTrailingClassArgumentRemoved); + if (last.type().passByValue()) + flags.setFlag(AbstractMetaFunction::Flag::OperatorClassArgumentByValue); + } + metaFunction->setFlags(flags); + metaFunction->setAccess(Access::Public); + AbstractMetaClass::addFunction(baseoperandClass, AbstractMetaFunctionCPtr(metaFunction)); + if (!metaFunction->arguments().isEmpty()) { + const auto include = metaFunction->arguments().constFirst().type().typeEntry()->include(); + baseoperandClass->typeEntry()->addArgumentInclude(include); + } + Q_ASSERT(!metaFunction->wasPrivate()); +} + +bool AbstractMetaBuilderPrivate::traverseStreamOperator(const FunctionModelItem &item, + const AbstractMetaClassPtr ¤tClass) +{ + ArgumentList itemArguments = item->arguments(); + if (itemArguments.size() != 2 || item->accessPolicy() != Access::Public) + return false; + auto streamClass = argumentToClass(itemArguments.at(0), currentClass); + if (streamClass == nullptr || !streamClass->isStream()) + return false; + auto streamedClass = argumentToClass(itemArguments.at(1), currentClass); + if (streamedClass == nullptr) + return false; + + AbstractMetaFunction *streamFunction = traverseFunction(item, streamedClass); + if (!streamFunction) + return false; + + // Strip first argument, since that is the containing object + AbstractMetaArgumentList arguments = streamFunction->arguments(); + if (!streamClass->typeEntry()->generateCode()) { + arguments.takeLast(); + } else { + arguments.takeFirst(); + fixArgumentIndexes(&arguments); + } + + streamFunction->setArguments(arguments); + + streamFunction->setAccess(Access::Public); + + AbstractMetaClassPtr funcClass; + + if (!streamClass->typeEntry()->generateCode()) { + AbstractMetaArgumentList reverseArgs = streamFunction->arguments(); + std::reverse(reverseArgs.begin(), reverseArgs.end()); + fixArgumentIndexes(&reverseArgs); + streamFunction->setArguments(reverseArgs); + streamFunction->setReverseOperator(true); + funcClass = streamedClass; + } else { + funcClass = streamClass; + } + + AbstractMetaClass::addFunction(funcClass, AbstractMetaFunctionCPtr(streamFunction)); + auto funcTe = funcClass->typeEntry(); + if (funcClass == streamClass) + funcTe->addArgumentInclude(streamedClass->typeEntry()->include()); + else + funcTe->addArgumentInclude(streamClass->typeEntry()->include()); + return true; +} + +static bool metaEnumLessThan(const AbstractMetaEnum &e1, const AbstractMetaEnum &e2) +{ return e1.fullName() < e2.fullName(); } + +static bool metaClassLessThan(const AbstractMetaClassCPtr &c1, const AbstractMetaClassCPtr &c2) +{ return c1->fullName() < c2->fullName(); } + +static bool metaFunctionLessThan(const AbstractMetaFunctionCPtr &f1, const AbstractMetaFunctionCPtr &f2) +{ return f1->name() < f2->name(); } + +void AbstractMetaBuilderPrivate::sortLists() +{ + // Ensure indepedent classes are in alphabetical order, + std::sort(m_metaClasses.begin(), m_metaClasses.end(), metaClassLessThan); + // this is a temporary solution before new type revision implementation + // We need move QMetaObject register before QObject. + Dependencies additionalDependencies; + if (auto qObjectClass = AbstractMetaClass::findClass(m_metaClasses, "QObject")) { + if (auto qMetaObjectClass = AbstractMetaClass::findClass(m_metaClasses, "QMetaObject")) { + Dependency dependency; + dependency.parent = qMetaObjectClass; + dependency.child = qObjectClass; + additionalDependencies.append(dependency); + } + } + m_metaClasses = classesTopologicalSorted(m_metaClasses, additionalDependencies); + + for (const auto &cls : std::as_const(m_metaClasses)) + cls->sortFunctions(); + + // Ensure that indexes are in alphabetical order, roughly, except + // for classes, which are in topological order + std::sort(m_globalEnums.begin(), m_globalEnums.end(), metaEnumLessThan); + std::sort(m_templates.begin(), m_templates.end(), metaClassLessThan); + std::sort(m_smartPointers.begin(), m_smartPointers.end(), metaClassLessThan); + std::sort(m_globalFunctions.begin(), m_globalFunctions.end(), metaFunctionLessThan); +} + +FileModelItem AbstractMetaBuilderPrivate::buildDom(QByteArrayList arguments, + bool addCompilerSupportArguments, + LanguageLevel level, + unsigned clangFlags) +{ + clang::Builder builder; + builder.setForceProcessSystemIncludes(TypeDatabase::instance()->forceProcessSystemIncludes()); + if (addCompilerSupportArguments) { + if (level == LanguageLevel::Default) + level = clang::emulatedCompilerLanguageLevel(); + arguments.prepend(QByteArrayLiteral("-std=") + + clang::languageLevelOption(level)); + } + FileModelItem result = clang::parse(arguments, addCompilerSupportArguments, + level, clangFlags, builder) + ? builder.dom() : FileModelItem(); + const clang::BaseVisitor::Diagnostics &diagnostics = builder.diagnostics(); + if (const auto diagnosticsCount = diagnostics.size()) { + QDebug d = qWarning(); + d.nospace(); + d.noquote(); + d << "Clang: " << diagnosticsCount << " diagnostic messages:\n"; + for (qsizetype i = 0; i < diagnosticsCount; ++i) + d << " " << diagnostics.at(i) << '\n'; + } + return result; +} + +// List of candidates for a mismatched added global function. +static QStringList functionCandidates(const AbstractMetaFunctionCList &list, + const QString &signature) +{ + QString name = signature; + const auto parenPos = name.indexOf(u'('); + if (parenPos > 0) + name.truncate(parenPos); + QStringList result; + for (const auto &func : list) { + if (name == func->name()) + result += func->minimalSignature(); + } + return result; +} + +void AbstractMetaBuilderPrivate::traverseDom(const FileModelItem &dom, + ApiExtractorFlags flags) +{ + const TypeDatabase *types = TypeDatabase::instance(); + + pushScope(dom); + + // Start the generation... + const ClassList &typeValues = dom->classes(); + + ReportHandler::startProgress("Generated class model (" + + QByteArray::number(typeValues.size()) + ")."); + for (const ClassModelItem &item : typeValues) { + if (const auto cls = traverseClass(dom, item, nullptr)) + addAbstractMetaClass(cls, item.get()); + } + + // We need to know all global enums + const EnumList &enums = dom->enums(); + + ReportHandler::startProgress("Generated enum model (" + + QByteArray::number(enums.size()) + ")."); + for (const EnumModelItem &item : enums) { + auto metaEnum = traverseEnum(item, nullptr, QSet<QString>()); + if (metaEnum.has_value()) { + if (metaEnum->typeEntry()->generateCode()) + m_globalEnums << metaEnum.value(); + } + } + + const auto &namespaceTypeValues = dom->namespaces(); + ReportHandler::startProgress("Generated namespace model (" + + QByteArray::number(namespaceTypeValues.size()) + ")."); + for (const NamespaceModelItem &item : namespaceTypeValues) + traverseNamespace(dom, item); + + // Go through all typedefs to see if we have defined any + // specific typedefs to be used as classes. + const TypeDefList typeDefs = dom->typeDefs(); + ReportHandler::startProgress("Resolved typedefs (" + + QByteArray::number(typeDefs.size()) + ")."); + for (const TypeDefModelItem &typeDef : typeDefs) { + if (const auto cls = traverseTypeDef(dom, typeDef, nullptr)) + addAbstractMetaClass(cls, typeDef.get()); + } + + traverseTypesystemTypedefs(); + + for (const ClassModelItem &item : typeValues) + traverseClassMembers(item); + + for (const NamespaceModelItem &item : namespaceTypeValues) + traverseNamespaceMembers(item); + + // Global functions + const FunctionList &functions = dom->functions(); + for (const FunctionModelItem &func : functions) { + if (func->accessPolicy() != Access::Public || func->name().startsWith(u"operator")) + continue; + + FunctionTypeEntryPtr funcEntry = types->findFunctionType(func->name()); + if (!funcEntry || !funcEntry->generateCode()) + continue; + + AbstractMetaFunction *metaFunc = traverseFunction(func, nullptr); + if (!metaFunc) + continue; + + AbstractMetaFunctionCPtr metaFuncPtr(metaFunc); + if (!funcEntry->hasSignature(metaFunc->minimalSignature())) + continue; + + metaFunc->setTypeEntry(funcEntry); + applyFunctionModifications(metaFunc); + metaFunc->applyTypeModifications(); + + setInclude(funcEntry, func->fileName()); + + m_globalFunctions << metaFuncPtr; + } + + ReportHandler::startProgress("Fixed class inheritance."); + for (const auto &cls : std::as_const(m_metaClasses)) { + if (cls->needsInheritanceSetup()) { + setupInheritance(cls); + traverseUsingMembers(cls); + if (cls->templateBaseClass()) + inheritTemplateFunctions(cls); + if (!cls->hasVirtualDestructor() && cls->baseClass() + && cls->baseClass()->hasVirtualDestructor()) + cls->setHasVirtualDestructor(true); + } + } + + ReportHandler::startProgress("Checked for inconsistencies in class model."); + for (const auto &cls : std::as_const(m_metaClasses)) { + AbstractMetaClass::fixFunctions(cls); + + if (cls->canAddDefaultConstructor()) + AbstractMetaClass::addDefaultConstructor(cls); + if (cls->canAddDefaultCopyConstructor()) + AbstractMetaClass::addDefaultCopyConstructor(cls); + + const bool avoidProtectedHack = flags.testFlag(ApiExtractorFlag::AvoidProtectedHack); + const bool vco = + AbstractMetaClass::determineValueTypeWithCopyConstructorOnly(cls, avoidProtectedHack); + cls->setValueTypeWithCopyConstructorOnly(vco); + cls->typeEntry()->setValueTypeWithCopyConstructorOnly(vco); + } + + const auto &allEntries = types->entries(); + + ReportHandler::startProgress("Checked for inconsistencies in typesystem (" + + QByteArray::number(allEntries.size()) + ")."); + for (auto it = allEntries.cbegin(), end = allEntries.cend(); it != end; ++it) { + const TypeEntryPtr &entry = it.value(); + if (!entry->isPrimitive()) { + if ((entry->isValue() || entry->isObject()) + && !types->shouldDropTypeEntry(entry->qualifiedCppName()) + && !entry->isContainer() + && !entry->isCustom() + && entry->generateCode() + && !AbstractMetaClass::findClass(m_metaClasses, entry)) { + qCWarning(lcShiboken, "%s", qPrintable(msgTypeNotDefined(entry))); + } else if (entry->generateCode() && entry->type() == TypeEntry::FunctionType) { + auto fte = std::static_pointer_cast<const FunctionTypeEntry>(entry); + const QStringList &signatures = fte->signatures(); + for (const QString &signature : signatures) { + bool ok = false; + for (const auto &func : std::as_const(m_globalFunctions)) { + if (signature == func->minimalSignature()) { + ok = true; + break; + } + } + if (!ok) { + const QStringList candidates = functionCandidates(m_globalFunctions, + signatures.constFirst()); + qCWarning(lcShiboken, "%s", + qPrintable(msgGlobalFunctionNotDefined(fte, signature, candidates))); + } + } + } else if (entry->isEnum() && entry->generateCode()) { + const auto enumEntry = std::static_pointer_cast<const EnumTypeEntry>(entry); + const auto cls = AbstractMetaClass::findClass(m_metaClasses, + enumEntry->parent()); + + const bool enumFound = cls + ? cls->findEnum(entry->targetLangEntryName()).has_value() + : m_enums.contains(entry); + + if (!enumFound) { + entry->setCodeGeneration(TypeEntry::GenerateNothing); + qCWarning(lcShiboken, "%s", + qPrintable(msgEnumNotDefined(enumEntry))); + } + + } + } + } + + { + const FunctionList &hashFunctions = dom->findFunctions("qHash"); + for (const FunctionModelItem &item : hashFunctions) + registerHashFunction(item, nullptr); + } + + registerToStringCapabilityIn(dom); + + for (const auto &func : dom->functions()) { + switch (func->functionType()) { + case CodeModel::ComparisonOperator: + case CodeModel::ArithmeticOperator: + case CodeModel::BitwiseOperator: + case CodeModel::LogicalOperator: + traverseOperatorFunction(func, nullptr); + break; + case CodeModel::ShiftOperator: + if (!traverseStreamOperator(func, nullptr)) + traverseOperatorFunction(func, nullptr); + default: + break; + } + } + + ReportHandler::startProgress("Checked for inconsistencies in function modifications."); + + checkFunctionModifications(); + + ReportHandler::startProgress("Wrote log files."); + + for (const auto &cls : std::as_const(m_metaClasses)) { +// setupEquals(cls); +// setupComparable(cls); + setupExternalConversion(cls); + + // sort all inner classes topologically + if (!cls->typeEntry()->codeGeneration() || cls->innerClasses().size() < 2) + continue; + + cls->setInnerClasses(classesTopologicalSorted(cls->innerClasses())); + } + + fixSmartPointers(); + + dumpLog(); + + sortLists(); + + // Functions added to the module on the type system. + QString errorMessage; + const AddedFunctionList &globalUserFunctions = types->globalUserFunctions(); + for (const AddedFunctionPtr &addedFunc : globalUserFunctions) { + if (!traverseAddedGlobalFunction(addedFunc, &errorMessage)) + throw Exception(errorMessage); + } + + if (!m_codeModelTestMode) { + m_itemToClass.clear(); + m_classToItem.clear(); + m_typeSystemTypeDefs.clear(); + m_scopes.clear(); + } + + ReportHandler::endProgress(); +} + +bool AbstractMetaBuilder::build(const QByteArrayList &arguments, + ApiExtractorFlags apiExtractorFlags, + bool addCompilerSupportArguments, + LanguageLevel level, + unsigned clangFlags) +{ + const FileModelItem dom = d->buildDom(arguments, addCompilerSupportArguments, + level, clangFlags); + if (!dom) + return false; + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) + qCDebug(lcShiboken) << dom.get(); + d->traverseDom(dom, apiExtractorFlags); + + return true; +} + +void AbstractMetaBuilder::setLogDirectory(const QString &logDir) +{ + d->m_logDirectory = logDir; + if (!d->m_logDirectory.endsWith(QDir::separator())) + d->m_logDirectory.append(QDir::separator()); +} + +void AbstractMetaBuilderPrivate::addAbstractMetaClass(const AbstractMetaClassPtr &cls, + const _CodeModelItem *item) +{ + m_itemToClass.insert(item, cls); + m_classToItem.insert(cls, item); + if (cls->typeEntry()->isContainer()) { + m_templates << cls; + } else if (cls->typeEntry()->isSmartPointer()) { + m_smartPointers << cls; + } else { + m_metaClasses << cls; + } +} + +AbstractMetaClassPtr + AbstractMetaBuilderPrivate::traverseNamespace(const FileModelItem &dom, + const NamespaceModelItem &namespaceItem) +{ + QString namespaceName = currentScope()->qualifiedName().join(u"::"_s); + if (!namespaceName.isEmpty()) + namespaceName.append(u"::"_s); + namespaceName.append(namespaceItem->name()); + + if (TypeDatabase::instance()->isClassRejected(namespaceName)) { + m_rejectedClasses.insert({AbstractMetaBuilder::GenerationDisabled, + namespaceName, namespaceName, QString{}}); + return {}; + } + + auto type = TypeDatabase::instance()->findNamespaceType(namespaceName, namespaceItem->fileName()); + if (!type) { + const QString rejectReason = msgNamespaceNoTypeEntry(namespaceItem, namespaceName); + qCWarning(lcShiboken, "%s", qPrintable(rejectReason)); + m_rejectedClasses.insert({AbstractMetaBuilder::GenerationDisabled, + namespaceName, namespaceName, rejectReason}); + return nullptr; + } + + if (namespaceItem->type() == NamespaceType::Inline) { + type->setInlineNamespace(true); + TypeDatabase::instance()->addInlineNamespaceLookups(type); + } + + // Continue populating namespace? + AbstractMetaClassPtr metaClass = AbstractMetaClass::findClass(m_metaClasses, type); + if (!metaClass) { + metaClass.reset(new AbstractMetaClass); + metaClass->setTypeEntry(type); + addAbstractMetaClass(metaClass, namespaceItem.get()); + if (auto extendsType = type->extends()) { + const auto extended = AbstractMetaClass::findClass(m_metaClasses, extendsType); + if (!extended) { + qCWarning(lcShiboken, "%s", + qPrintable(msgNamespaceToBeExtendedNotFound(extendsType->name(), extendsType->targetLangPackage()))); + return {}; + } + metaClass->setExtendedNamespace(extended); + } + } else { + m_itemToClass.insert(namespaceItem.get(), metaClass); + } + + traverseEnums(namespaceItem, metaClass, namespaceItem->enumsDeclarations()); + + pushScope(namespaceItem); + + const ClassList &classes = namespaceItem->classes(); + for (const ClassModelItem &cls : classes) { + const auto mjc = traverseClass(dom, cls, metaClass); + if (mjc) { + metaClass->addInnerClass(mjc); + mjc->setEnclosingClass(metaClass); + addAbstractMetaClass(mjc, cls.get()); + } + } + + // Go through all typedefs to see if we have defined any + // specific typedefs to be used as classes. + const TypeDefList typeDefs = namespaceItem->typeDefs(); + for (const TypeDefModelItem &typeDef : typeDefs) { + const auto cls = traverseTypeDef(dom, typeDef, metaClass); + if (cls) { + metaClass->addInnerClass(cls); + cls->setEnclosingClass(metaClass); + addAbstractMetaClass(cls, typeDef.get()); + } + } + + // Traverse namespaces recursively + for (const NamespaceModelItem &ni : namespaceItem->namespaces()) { + const auto mjc = traverseNamespace(dom, ni); + if (mjc) { + metaClass->addInnerClass(mjc); + mjc->setEnclosingClass(metaClass); + m_classToItem.insert(mjc, ni.get()); // Add for enum lookup. + m_itemToClass.insert(ni.get(), mjc); + } + } + + popScope(); + + if (!type->include().isValid()) + setInclude(type, namespaceItem->fileName()); + + return metaClass; +} + +std::optional<AbstractMetaEnum> + AbstractMetaBuilderPrivate::traverseEnum(const EnumModelItem &enumItem, + const AbstractMetaClassPtr &enclosing, + const QSet<QString> &enumsDeclarations) +{ + QString qualifiedName = enumItem->qualifiedName().join(u"::"_s); + + TypeEntryPtr typeEntry; + const auto enclosingTypeEntry = enclosing ? enclosing->typeEntry() : TypeEntryCPtr{}; + if (enumItem->accessPolicy() == Access::Private) { + typeEntry.reset(new EnumTypeEntry(enumItem->qualifiedName().constLast(), + QVersionNumber(0, 0), enclosingTypeEntry)); + TypeDatabase::instance()->addType(typeEntry); + } else if (enumItem->enumKind() != AnonymousEnum) { + typeEntry = TypeDatabase::instance()->findType(qualifiedName); + } else { + QStringList tmpQualifiedName = enumItem->qualifiedName(); + const EnumeratorList &enums = enumItem->enumerators(); + for (const EnumeratorModelItem &enumValue : enums) { + tmpQualifiedName.removeLast(); + tmpQualifiedName << enumValue->name(); + qualifiedName = tmpQualifiedName.join(u"::"_s); + typeEntry = TypeDatabase::instance()->findType(qualifiedName); + if (typeEntry) + break; + } + } + + QString enumName = enumItem->name(); + + QString className; + if (enclosingTypeEntry) + className = enclosingTypeEntry->qualifiedCppName(); + + QString rejectReason; + if (TypeDatabase::instance()->isEnumRejected(className, enumName, &rejectReason)) { + if (typeEntry) + typeEntry->setCodeGeneration(TypeEntry::GenerateNothing); + m_rejectedEnums.insert({AbstractMetaBuilder::GenerationDisabled, qualifiedName, + qualifiedName, rejectReason}); + return {}; + } + + const bool rejectionWarning = !enclosing || enclosing->typeEntry()->generateCode(); + + if (!typeEntry) { + const QString rejectReason = msgNoEnumTypeEntry(enumItem, className); + if (rejectionWarning) + qCWarning(lcShiboken, "%s", qPrintable(rejectReason)); + m_rejectedEnums.insert({AbstractMetaBuilder::NotInTypeSystem, qualifiedName, + qualifiedName, rejectReason}); + return {}; + } + + if (!typeEntry->isEnum()) { + const QString rejectReason = msgNoEnumTypeConflict(enumItem, className, typeEntry); + if (rejectionWarning) + qCWarning(lcShiboken, "%s", qPrintable(rejectReason)); + m_rejectedEnums.insert({AbstractMetaBuilder::NotInTypeSystem, qualifiedName, + qualifiedName, rejectReason}); + return {}; + } + + AbstractMetaEnum metaEnum; + metaEnum.setEnumKind(enumItem->enumKind()); + metaEnum.setDeprecated(enumItem->isDeprecated()); + metaEnum.setUnderlyingType(enumItem->underlyingType()); + metaEnum.setSigned(enumItem->isSigned()); + if (enumsDeclarations.contains(qualifiedName) + || enumsDeclarations.contains(enumName)) { + metaEnum.setHasQEnumsDeclaration(true); + } + + auto enumTypeEntry = std::static_pointer_cast<EnumTypeEntry>(typeEntry); + metaEnum.setTypeEntry(enumTypeEntry); + metaEnum.setAccess(enumItem->accessPolicy()); + if (metaEnum.access() == Access::Private) + typeEntry->setCodeGeneration(TypeEntry::GenerateNothing); + // PYSIDE-2088, MSVC signedness issue in Qt + const bool castToUnsigned = enumItem->isSigned() + && enumTypeEntry->cppType().contains(u"unsigned"_s); + const EnumeratorList &enums = enumItem->enumerators(); + for (const EnumeratorModelItem &valueItem : enums) { + + AbstractMetaEnumValue metaEnumValue; + metaEnumValue.setName(valueItem->name()); + // Deciding the enum value... + + metaEnumValue.setStringValue(valueItem->stringValue()); + const auto value = valueItem->value(); + metaEnumValue.setValue(castToUnsigned ? value.toUnsigned() : value); + metaEnumValue.setDeprecated(valueItem->isDeprecated()); + metaEnum.addEnumValue(metaEnumValue); + } + + if (!metaEnum.typeEntry()->include().isValid()) { + auto te = std::const_pointer_cast<EnumTypeEntry>(metaEnum.typeEntry()); + setInclude(te, enumItem->fileName()); + } + + // Register all enum values on Type database + const bool isScopedEnum = enumItem->enumKind() == EnumClass; + const EnumeratorList &enumerators = enumItem->enumerators(); + for (const EnumeratorModelItem &e : enumerators) { + auto enumValue = std::make_shared<EnumValueTypeEntry>(e->name(), e->stringValue(), + enumTypeEntry, isScopedEnum, + enumTypeEntry->version()); + TypeDatabase::instance()->addType(enumValue); + if (e->value().isNullValue()) + enumTypeEntry->setNullValue(enumValue); + } + + metaEnum.setEnclosingClass(enclosing); + m_enums.insert(typeEntry, metaEnum); + + return metaEnum; +} + +AbstractMetaClassPtr + AbstractMetaBuilderPrivate::traverseTypeDef(const FileModelItem &dom, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass) +{ + auto result = traverseTypeDefHelper(dom, typeDef, currentClass); + if (!result && typeDef->type().isPlain()) { + const auto &type = typeDef->type(); + QString fullName; + if (currentClass) + fullName += currentClass->qualifiedCppName() + "::"_L1; + fullName += typeDef->name(); + QString targetName = typeDef->type().toString(); + m_typedefTargetToName.insert(targetName, fullName); + const QByteArray normalized = QMetaObject::normalizedType(targetName.toUtf8().constData()); + if (targetName != QLatin1StringView(normalized)) + m_typedefTargetToName.insert(QString::fromUtf8(normalized), fullName); + } + return result; +} + +AbstractMetaClassPtr + AbstractMetaBuilderPrivate::traverseTypeDefHelper(const FileModelItem &, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass) +{ + TypeDatabase *types = TypeDatabase::instance(); + QString className = stripTemplateArgs(typeDef->name()); + + QString fullClassName = className; + // we have an inner class + if (currentClass) { + fullClassName = stripTemplateArgs(currentClass->typeEntry()->qualifiedCppName()) + + u"::"_s + fullClassName; + } + + // If this is the alias for a primitive type + // we store the aliased type on the alias + // TypeEntry + const auto ptype = types->findPrimitiveType(className); + const auto &targetNames = typeDef->type().qualifiedName(); + const auto pTarget = targetNames.size() == 1 + ? types->findPrimitiveType(targetNames.constFirst()) : PrimitiveTypeEntryPtr{}; + if (ptype) { + ptype->setReferencedTypeEntry(pTarget); + return nullptr; + } + + // It is a (nested?) global typedef to a primitive type + // (like size_t = unsigned)? Add it to the type DB. + if (pTarget && isCppPrimitive(basicReferencedNonBuiltinTypeEntry(pTarget)) + && currentClass == nullptr) { + auto pte = std::make_shared<PrimitiveTypeEntry>(className, QVersionNumber{}, + TypeEntryCPtr{}); + pte->setReferencedTypeEntry(pTarget); + pte->setBuiltIn(true); + types->addType(pte); + return nullptr; + } + + // If we haven't specified anything for the typedef, then we don't care + auto type = types->findComplexType(fullClassName); + if (!type) + return nullptr; + + auto metaClass = std::make_shared<AbstractMetaClass>(); + metaClass->setTypeDef(true); + metaClass->setTypeEntry(type); + metaClass->setBaseClassNames(QStringList(typeDef->type().toString())); + + // Set the default include file name + if (!type->include().isValid()) + setInclude(type, typeDef->fileName()); + + fillAddedFunctions(metaClass); + + return metaClass; +} + +// Add the typedef'ed classes +void AbstractMetaBuilderPrivate::traverseTypesystemTypedefs() +{ + const auto &entries = TypeDatabase::instance()->typedefEntries(); + for (auto it = entries.begin(), end = entries.end(); it != end; ++it) { + const TypedefEntryPtr &te = it.value(); + auto metaClass = std::make_shared<AbstractMetaClass>(); + metaClass->setTypeDef(true); + metaClass->setTypeEntry(te->target()); + metaClass->setBaseClassNames(QStringList(te->sourceType())); + fillAddedFunctions(metaClass); + addAbstractMetaClass(metaClass, nullptr); + // Ensure base classes are set up when traversing functions for the + // type to be resolved. + if (setupInheritance(metaClass)) { + // Create an entry to look up up types obtained from parsing + // functions in reverse. As opposed to container specializations, + // which are generated into every instantiating module (indicated + // by ContainerTypeEntry::targetLangPackage() being empty), the + // correct index array of the module needs to be found by reverse + // mapping the instantiations to the typedef entry. + // Synthesize a AbstractMetaType which would be found by an + // instantiation. + AbstractMetaType sourceType; + sourceType.setTypeEntry(metaClass->templateBaseClass()->typeEntry()); + sourceType.setInstantiations(metaClass->templateBaseClassInstantiations()); + sourceType.decideUsagePattern(); + m_typeSystemTypeDefs.append({sourceType, metaClass}); + } + } +} + +AbstractMetaClassPtr AbstractMetaBuilderPrivate::traverseClass(const FileModelItem &dom, + const ClassModelItem &classItem, + const AbstractMetaClassPtr ¤tClass) +{ + QString className = stripTemplateArgs(classItem->name()); + QString fullClassName = className; + + // we have inner an class + if (currentClass) { + fullClassName = stripTemplateArgs(currentClass->typeEntry()->qualifiedCppName()) + + u"::"_s + fullClassName; + } + + const auto type = TypeDatabase::instance()->findComplexType(fullClassName); + AbstractMetaBuilder::RejectReason reason = AbstractMetaBuilder::NoReason; + + if (TypeDatabase::instance()->isClassRejected(fullClassName)) { + reason = AbstractMetaBuilder::GenerationDisabled; + } else if (!type) { + TypeEntryPtr te = TypeDatabase::instance()->findType(fullClassName); + if (te && !te->isComplex()) { + reason = AbstractMetaBuilder::RedefinedToNotClass; + // Set the default include file name + if (!te->include().isValid()) + setInclude(te, classItem->fileName()); + } else { + reason = AbstractMetaBuilder::NotInTypeSystem; + } + } else if (type->codeGeneration() == TypeEntry::GenerateNothing) { + reason = AbstractMetaBuilder::GenerationDisabled; + } + if (reason != AbstractMetaBuilder::NoReason) { + if (fullClassName.isEmpty()) { + QTextStream(&fullClassName) << "anonymous struct at " << classItem->fileName() + << ':' << classItem->startLine(); + } + m_rejectedClasses.insert({reason, fullClassName, fullClassName, QString{}}); + return nullptr; + } + + auto metaClass = std::make_shared<AbstractMetaClass>(); + metaClass->setSourceLocation(classItem->sourceLocation()); + metaClass->setTypeEntry(type); + if ((type->typeFlags() & ComplexTypeEntry::ForceAbstract) != 0) + *metaClass += AbstractMetaClass::Abstract; + + if (classItem->isFinal()) + *metaClass += AbstractMetaClass::FinalCppClass; + + if (classItem->classType() == CodeModel::Struct) + *metaClass += AbstractMetaClass::Struct; + + QStringList baseClassNames; + const QList<_ClassModelItem::BaseClass> &baseClasses = classItem->baseClasses(); + for (const _ClassModelItem::BaseClass &baseClass : baseClasses) { + if (baseClass.accessPolicy == Access::Public) + baseClassNames.append(baseClass.name); + } + + metaClass->setBaseClassNames(baseClassNames); + if (type->stream()) + metaClass->setStream(true); + + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) { + const QString message = type->isContainer() + ? u"container: '"_s + fullClassName + u'\'' + : u"class: '"_s + metaClass->fullName() + u'\''; + qCInfo(lcShiboken, "%s", qPrintable(message)); + } + + TemplateParameterList template_parameters = classItem->templateParameters(); + TypeEntryCList template_args; + template_args.clear(); + auto argumentParent = typeSystemTypeEntry(metaClass->typeEntry()); + for (qsizetype i = 0; i < template_parameters.size(); ++i) { + const TemplateParameterModelItem ¶m = template_parameters.at(i); + auto param_type = + std::make_shared<TemplateArgumentEntry>(param->name(), type->version(), + argumentParent); + param_type->setOrdinal(i); + template_args.append(TypeEntryCPtr(param_type)); + } + metaClass->setTemplateArguments(template_args); + + parseQ_Properties(metaClass, classItem->propertyDeclarations()); + + traverseEnums(classItem, metaClass, classItem->enumsDeclarations()); + + // Inner classes + { + const ClassList &innerClasses = classItem->classes(); + for (const ClassModelItem &ci : innerClasses) { + const auto cl = traverseClass(dom, ci, metaClass); + if (cl) { + cl->setEnclosingClass(metaClass); + metaClass->addInnerClass(cl); + addAbstractMetaClass(cl, ci.get()); + } + } + + } + + // Go through all typedefs to see if we have defined any + // specific typedefs to be used as classes. + const TypeDefList typeDefs = classItem->typeDefs(); + for (const TypeDefModelItem &typeDef : typeDefs) { + const auto cls = traverseTypeDef(dom, typeDef, metaClass); + if (cls) { + cls->setEnclosingClass(metaClass); + addAbstractMetaClass(cls, typeDef.get()); + } + } + + // Set the default include file name + if (!type->include().isValid()) + setInclude(type, classItem->fileName()); + + return metaClass; +} + +void AbstractMetaBuilderPrivate::traverseScopeMembers(const ScopeModelItem &item, + const AbstractMetaClassPtr &metaClass) +{ + // Classes/Namespace members + traverseFields(item, metaClass); + traverseFunctions(item, metaClass); + + // Inner classes + const ClassList &innerClasses = item->classes(); + for (const ClassModelItem &ci : innerClasses) + traverseClassMembers(ci); +} + +void AbstractMetaBuilderPrivate::traverseClassMembers(const ClassModelItem &item) +{ + const auto metaClass = m_itemToClass.value(item.get()); + if (metaClass) // Class members + traverseScopeMembers(item, metaClass); +} + +void AbstractMetaBuilderPrivate::traverseUsingMembers(const AbstractMetaClassPtr &metaClass) const +{ + const _CodeModelItem *item = m_classToItem.value(metaClass); + if (item == nullptr || item->kind() != _CodeModelItem::Kind_Class) + return; + const auto *classItem = static_cast<const _ClassModelItem *>(item); + for (const auto &um : classItem->usingMembers()) { + QString className = um.className; + auto pos = className.indexOf(u'<'); // strip "QList<value>" + if (pos != -1) + className.truncate(pos); + if (auto baseClass = findBaseClass(metaClass, className)) { + QString name = um.memberName; + const auto lastQualPos = name.lastIndexOf(u"::"_s); + if (lastQualPos != -1) + name.remove(0, lastQualPos + 2); + metaClass->addUsingMember({name, baseClass, um.access}); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgUsingMemberClassNotFound(metaClass, um.className, + um.memberName))); + } + } +} + +void AbstractMetaBuilderPrivate::traverseNamespaceMembers(const NamespaceModelItem &item) +{ + const auto metaClass = m_itemToClass.value(item.get()); + if (!metaClass) + return; + + // Namespace members + traverseScopeMembers(item, metaClass); + + // Inner namespaces + for (const NamespaceModelItem &ni : item->namespaces()) + traverseNamespaceMembers(ni); + +} + +static inline QString fieldSignatureWithType(const VariableModelItem &field) +{ + return field->name() + " -> "_L1 + field->type().toString(); +} + +static inline QString qualifiedFieldSignatureWithType(const QString &className, + const VariableModelItem &field) +{ + return className + u"::"_s + fieldSignatureWithType(field); +} + +std::optional<AbstractMetaField> + AbstractMetaBuilderPrivate::traverseField(const VariableModelItem &field, + const AbstractMetaClassCPtr &cls) +{ + QString fieldName = field->name(); + QString className = cls->typeEntry()->qualifiedCppName(); + + // Ignore friend decl. + if (field->isFriend()) + return {}; + + if (field->accessPolicy() == Access::Private) + return {}; + + QString rejectReason; + if (TypeDatabase::instance()->isFieldRejected(className, fieldName, &rejectReason)) { + const QString signature = qualifiedFieldSignatureWithType(className, field); + m_rejectedFields.insert({AbstractMetaBuilder::GenerationDisabled, + signature, signature, rejectReason}); + return {}; + } + + + AbstractMetaField metaField; + metaField.setName(fieldName); + metaField.setEnclosingClass(cls); + + TypeInfo fieldType = field->type(); + auto metaType = translateType(fieldType, cls); + + if (!metaType.has_value()) { + const QString type = TypeInfo::resolveType(fieldType, currentScope()).qualifiedName().join(u"::"_s); + if (cls->typeEntry()->generateCode()) { + qCWarning(lcShiboken, "%s", + qPrintable(msgSkippingField(field, cls->name(), type))); + } + return {}; + } + + metaField.setType(metaType.value()); + + metaField.setStatic(field->isStatic()); + metaField.setAccess(field->accessPolicy()); + + return metaField; +} + +static bool applyFieldModifications(AbstractMetaField *f) +{ + const auto &modifications = f->modifications(); + for (const auto &mod : modifications) { + if (mod.isRemoved()) + return false; + if (mod.isRenameModifier()) { + f->setOriginalName(f->name()); + f->setName(mod.renamedToName()); + } else if (!mod.isReadable()) { + f->setGetterEnabled(false); + } else if (!mod.isWritable()) { + f->setSetterEnabled(false); + } + } + return true; +} + +void AbstractMetaBuilderPrivate::traverseFields(const ScopeModelItem &scope_item, + const AbstractMetaClassPtr &metaClass) +{ + const VariableList &variables = scope_item->variables(); + for (const VariableModelItem &field : variables) { + auto metaFieldO = traverseField(field, metaClass); + if (metaFieldO.has_value()) { + AbstractMetaField metaField = metaFieldO.value(); + if (applyFieldModifications(&metaField)) + metaClass->addField(metaField); + } + } +} + +void AbstractMetaBuilderPrivate::fixReturnTypeOfConversionOperator(AbstractMetaFunction *metaFunction) +{ + if (!metaFunction->isConversionOperator()) + return; + + TypeDatabase *types = TypeDatabase::instance(); + static const QRegularExpression operatorRegExp("^operator "_L1); + Q_ASSERT(operatorRegExp.isValid()); + QString castTo = metaFunction->name().remove(operatorRegExp).trimmed(); + + if (castTo.endsWith(u'&')) + castTo.chop(1); + if (castTo.startsWith(u"const ")) + castTo.remove(0, 6); + + TypeEntryPtr retType = types->findType(castTo); + if (!retType) + return; + + AbstractMetaType metaType(retType); + metaType.decideUsagePattern(); + metaFunction->setType(metaType); +} + +AbstractMetaFunctionRawPtrList + AbstractMetaBuilderPrivate::classFunctionList(const ScopeModelItem &scopeItem, + AbstractMetaClass::Attributes *constructorAttributes, + const AbstractMetaClassPtr ¤tClass) +{ + *constructorAttributes = {}; + AbstractMetaFunctionRawPtrList result; + const FunctionList &scopeFunctionList = scopeItem->functions(); + result.reserve(scopeFunctionList.size()); + const bool isNamespace = currentClass->isNamespace(); + for (const FunctionModelItem &function : scopeFunctionList) { + if (isNamespace && function->isOperator()) { + traverseOperatorFunction(function, currentClass); + } else if (function->isSpaceshipOperator() && !function->isDeleted()) { + if (currentClass) + AbstractMetaClass::addSynthesizedComparisonOperators(currentClass); + } else if (auto *metaFunction = traverseFunction(function, currentClass)) { + result.append(metaFunction); + } else if (!function->isDeleted() && function->functionType() == CodeModel::Constructor) { + auto arguments = function->arguments(); + *constructorAttributes |= AbstractMetaClass::HasRejectedConstructor; + if (arguments.isEmpty() || arguments.constFirst()->defaultValue()) + *constructorAttributes |= AbstractMetaClass::HasRejectedDefaultConstructor; + } + } + return result; +} + +void AbstractMetaBuilderPrivate::traverseFunctions(const ScopeModelItem& scopeItem, + const AbstractMetaClassPtr &metaClass) +{ + AbstractMetaClass::Attributes constructorAttributes; + const AbstractMetaFunctionRawPtrList functions = + classFunctionList(scopeItem, &constructorAttributes, metaClass); + metaClass->setAttributes(metaClass->attributes() | constructorAttributes); + + for (AbstractMetaFunction *metaFunction : functions) { + if (metaClass->isNamespace()) + metaFunction->setCppAttribute(FunctionAttribute::Static); + + const auto propertyFunction = metaClass->searchPropertyFunction(metaFunction->name()); + if (propertyFunction.index >= 0) { + QPropertySpec prop = metaClass->propertySpecs().at(propertyFunction.index); + switch (propertyFunction.function) { + case AbstractMetaClass::PropertyFunction::Read: + // Property reader must be in the form "<type> name()" + if (!metaFunction->isSignal() + && prop.typeEntry() == metaFunction->type().typeEntry() + && metaFunction->arguments().isEmpty()) { + *metaFunction += AbstractMetaFunction::PropertyReader; + metaFunction->setPropertySpecIndex(propertyFunction.index); + } + break; + case AbstractMetaClass::PropertyFunction::Write: + // Property setter must be in the form "void name(<type>)" + // Make sure the function was created with all arguments; some + // argument can be missing during the parsing because of errors + // in the typesystem. + if (metaFunction->isVoid() && metaFunction->arguments().size() == 1 + && (prop.typeEntry() == metaFunction->arguments().at(0).type().typeEntry())) { + *metaFunction += AbstractMetaFunction::PropertyWriter; + metaFunction->setPropertySpecIndex(propertyFunction.index); + } + break; + case AbstractMetaClass::PropertyFunction::Reset: + // Property resetter must be in the form "void name()" + if (metaFunction->isVoid() && metaFunction->arguments().isEmpty()) { + *metaFunction += AbstractMetaFunction::PropertyResetter; + metaFunction->setPropertySpecIndex(propertyFunction.index); + } + break; + case AbstractMetaClass::PropertyFunction::Notify: + if (metaFunction->isSignal()) { + *metaFunction += AbstractMetaFunction::PropertyNotify; + metaFunction->setPropertySpecIndex(propertyFunction.index); + } + } + } + + if (metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction + && metaFunction->isPrivate()) { + metaClass->setHasPrivateConstructor(true); + } + if (metaFunction->isConstructor() && !metaFunction->isPrivate()) // Including Copy CT + metaClass->setHasNonPrivateConstructor(true); + + if (!metaFunction->isDestructor() + && !(metaFunction->isPrivate() && metaFunction->functionType() == AbstractMetaFunction::ConstructorFunction)) { + + if (metaFunction->isSignal() && metaClass->hasSignal(metaFunction)) + qCWarning(lcShiboken, "%s", qPrintable(msgSignalOverloaded(metaClass, metaFunction))); + + if (metaFunction->isConversionOperator()) + fixReturnTypeOfConversionOperator(metaFunction); + + AbstractMetaClass::addFunction(metaClass, AbstractMetaFunctionCPtr(metaFunction)); + applyFunctionModifications(metaFunction); + } else if (metaFunction->isDestructor()) { + metaClass->setHasPrivateDestructor(metaFunction->isPrivate()); + metaClass->setHasProtectedDestructor(metaFunction->isProtected()); + metaClass->setHasVirtualDestructor(metaFunction->isVirtual()); + } + if (!metaFunction->ownerClass()) { + delete metaFunction; + metaFunction = nullptr; + } + } + + fillAddedFunctions(metaClass); +} + +void AbstractMetaBuilderPrivate::fillAddedFunctions(const AbstractMetaClassPtr &metaClass) +{ + // Add the functions added by the typesystem + QString errorMessage; + const AddedFunctionList &addedFunctions = metaClass->typeEntry()->addedFunctions(); + for (const AddedFunctionPtr &addedFunc : addedFunctions) { + if (!traverseAddedMemberFunction(addedFunc, metaClass, &errorMessage)) + throw Exception(qPrintable(errorMessage)); + } +} + +QString AbstractMetaBuilder::getSnakeCaseName(const QString &name) +{ + const auto size = name.size(); + if (size < 3) + return name; + QString result; + result.reserve(size + 4); + for (qsizetype i = 0; i < size; ++i) { + const QChar c = name.at(i); + if (c.isUpper()) { + if (i > 0) { + if (name.at(i - 1).isUpper()) + return name; // Give up at consecutive upper chars + result.append(u'_'); + } + result.append(c.toLower()); + } else { + result.append(c); + } + } + return result; +} + +// Names under which an item will be registered to Python depending on snakeCase +QStringList AbstractMetaBuilder::definitionNames(const QString &name, + TypeSystem::SnakeCase snakeCase) +{ + QStringList result; + switch (snakeCase) { + case TypeSystem::SnakeCase::Unspecified: + case TypeSystem::SnakeCase::Disabled: + result.append(name); + break; + case TypeSystem::SnakeCase::Enabled: + result.append(AbstractMetaBuilder::getSnakeCaseName(name)); + break; + case TypeSystem::SnakeCase::Both: + result.append(AbstractMetaBuilder::getSnakeCaseName(name)); + if (name != result.constFirst()) + result.append(name); + break; + } + return result; +} + +void AbstractMetaBuilderPrivate::applyFunctionModifications(AbstractMetaFunction *func) +{ + AbstractMetaFunction& funcRef = *func; + for (const FunctionModification &mod : func->modifications(func->implementingClass())) { + if (mod.isRenameModifier()) { + func->setOriginalName(func->name()); + func->setName(mod.renamedToName()); + } else if (mod.isAccessModifier()) { + if (mod.isPublic()) + funcRef.modifyAccess(Access::Public); + else if (mod.isProtected()) + funcRef.modifyAccess(Access::Protected); + else if (mod.isPrivate()) + funcRef.modifyAccess(Access::Private); + } + } +} + +bool AbstractMetaBuilderPrivate::setupInheritance(const AbstractMetaClassPtr &metaClass) +{ + if (metaClass->inheritanceDone()) + return true; + + metaClass->setInheritanceDone(true); + + QStringList baseClasses = metaClass->baseClassNames(); + + // we only support our own containers and ONLY if there is only one baseclass + if (baseClasses.size() == 1 && baseClasses.constFirst().contains(u'<')) { + TypeInfo info; + ComplexTypeEntryPtr baseContainerType; + const auto templ = findTemplateClass(baseClasses.constFirst(), metaClass, + &info, &baseContainerType); + if (templ) { + setupInheritance(templ); + if (!inheritTemplate(metaClass, templ, info)) + return false; + metaClass->typeEntry()->setBaseContainerType(templ->typeEntry()); + return true; + } + + if (baseContainerType) { + // Container types are not necessarily wrapped as 'real' classes, + // but there may still be classes derived from them. In such case, + // we still need to set the base container type in order to + // generate correct code for type conversion checking. + // + // Additionally, we consider this case as successfully setting up + // inheritance. + metaClass->typeEntry()->setBaseContainerType(baseContainerType); + return true; + } + + qCWarning(lcShiboken, "template baseclass '%s' of '%s' is not known", + qPrintable(baseClasses.constFirst()), + qPrintable(metaClass->name())); + return false; + } + + auto *types = TypeDatabase::instance(); + + for (const auto &baseClassName : baseClasses) { + if (!types->isClassRejected(baseClassName)) { + auto typeEntry = types->findType(baseClassName); + if (typeEntry == nullptr || !typeEntry->isComplex()) { + qCWarning(lcShiboken, "%s", + qPrintable(msgBaseNotInTypeSystem(metaClass, baseClassName))); + return false; + } + auto baseClass = AbstractMetaClass::findClass(m_metaClasses, typeEntry); + if (!baseClass) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnknownBase(metaClass, baseClassName))); + return false; + } + metaClass->addBaseClass(baseClass); + + setupInheritance(baseClass); + } + } + + // Super class set by attribute "default-superclass". + const QString defaultSuperclassName = metaClass->typeEntry()->defaultSuperclass(); + if (!defaultSuperclassName.isEmpty()) { + auto defaultSuper = AbstractMetaClass::findClass(m_metaClasses, defaultSuperclassName); + if (defaultSuper != nullptr) { + metaClass->setDefaultSuperclass(defaultSuper); + } else { + QString message; + QTextStream(&message) << "Class \"" << defaultSuperclassName + << R"(" specified as "default-superclass" of ")" << metaClass->name() + << "\" could not be found in the code model."; + qCWarning(lcShiboken, "%s", qPrintable(message)); + } + } + + return true; +} + +void AbstractMetaBuilderPrivate::traverseEnums(const ScopeModelItem &scopeItem, + const AbstractMetaClassPtr &metaClass, + const QStringList &enumsDeclarations) +{ + const EnumList &enums = scopeItem->enums(); + const QSet<QString> enumsDeclarationSet(enumsDeclarations.cbegin(), enumsDeclarations.cend()); + for (const EnumModelItem &enumItem : enums) { + auto metaEnum = traverseEnum(enumItem, metaClass, enumsDeclarationSet); + if (metaEnum.has_value()) { + metaClass->addEnum(metaEnum.value()); + } + } +} + +static void applyDefaultExpressionModifications(const FunctionModificationList &functionMods, + int i, AbstractMetaArgument *metaArg) +{ + // use replace/remove-default-expression for set default value + for (const auto &modification : functionMods) { + for (const auto &argumentModification : modification.argument_mods()) { + if (argumentModification.index() == i + 1) { + if (argumentModification.removedDefaultExpression()) { + metaArg->setDefaultValueExpression(QString()); + break; + } + if (!argumentModification.replacedDefaultExpression().isEmpty()) { + metaArg->setDefaultValueExpression(argumentModification.replacedDefaultExpression()); + break; + } + } + } + } +} + +static AbstractMetaFunction::FunctionType functionTypeFromName(const QString &); + +bool AbstractMetaBuilderPrivate::traverseAddedGlobalFunction(const AddedFunctionPtr &addedFunc, + QString *errorMessage) +{ + AbstractMetaFunction *metaFunction = + traverseAddedFunctionHelper(addedFunc, nullptr, errorMessage); + if (metaFunction == nullptr) + return false; + m_globalFunctions << AbstractMetaFunctionCPtr(metaFunction); + return true; +} + +AbstractMetaFunction * + AbstractMetaBuilderPrivate::traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc, + const AbstractMetaClassPtr &metaClass /* = {} */, + QString *errorMessage) +{ + auto returnType = translateType(addedFunc->returnType(), metaClass, {}, errorMessage); + if (!returnType.has_value()) { + *errorMessage = + msgAddedFunctionInvalidReturnType(addedFunc->name(), + addedFunc->returnType().qualifiedName(), + *errorMessage, metaClass); + return nullptr; + } + + auto *metaFunction = new AbstractMetaFunction(addedFunc); + metaFunction->setType(returnType.value()); + metaFunction->setFunctionType(functionTypeFromName(addedFunc->name())); + + const auto &args = addedFunc->arguments(); + + qsizetype argCount = args.size(); + // Check "foo(void)" + if (argCount == 1 && args.constFirst().typeInfo.isVoid()) + argCount = 0; + for (qsizetype i = 0; i < argCount; ++i) { + const AddedFunction::Argument &arg = args.at(i); + auto type = translateType(arg.typeInfo, metaClass, {}, errorMessage); + if (Q_UNLIKELY(!type.has_value())) { + *errorMessage = + msgAddedFunctionInvalidArgType(addedFunc->name(), + arg.typeInfo.qualifiedName(), i + 1, + *errorMessage, metaClass); + delete metaFunction; + return nullptr; + } + type->decideUsagePattern(); + + AbstractMetaArgument metaArg; + if (!args.at(i).name.isEmpty()) + metaArg.setName(args.at(i).name); + metaArg.setType(type.value()); + metaArg.setArgumentIndex(i); + metaArg.setDefaultValueExpression(arg.defaultValue); + metaArg.setOriginalDefaultValueExpression(arg.defaultValue); + metaFunction->addArgument(metaArg); + } + + AbstractMetaArgumentList &metaArguments = metaFunction->arguments(); + + if (metaFunction->isOperatorOverload() && !metaFunction->isCallOperator()) { + if (metaArguments.size() > 2) { + qCWarning(lcShiboken) << "An operator overload need to have 0, 1 or 2 arguments if it's reverse."; + } else if (metaArguments.size() == 2) { + // Check if it's a reverse operator + if (metaArguments[1].type().typeEntry() == metaClass->typeEntry()) { + metaFunction->setReverseOperator(true); + // we need to call these two function to cache the old signature (with two args) + // we do this buggy behaviour to comply with the original apiextractor buggy behaviour. + metaFunction->signature(); + metaFunction->minimalSignature(); + metaArguments.removeLast(); + metaFunction->setArguments(metaArguments); + } else { + qCWarning(lcShiboken) << "Operator overload can have two arguments only if it's a reverse operator!"; + } + } + } + + + // Find the correct default values + const FunctionModificationList functionMods = metaFunction->modifications(metaClass); + applyCachedFunctionModifications(metaFunction, functionMods); + for (qsizetype i = 0; i < metaArguments.size(); ++i) { + AbstractMetaArgument &metaArg = metaArguments[i]; + + // use replace-default-expression for set default value + applyDefaultExpressionModifications(functionMods, i, &metaArg); + metaArg.setOriginalDefaultValueExpression(metaArg.defaultValueExpression()); // appear unmodified + } + + if (!metaArguments.isEmpty()) + fixArgumentNames(metaFunction, metaFunction->modifications(metaClass)); + + return metaFunction; +} + +bool AbstractMetaBuilderPrivate::traverseAddedMemberFunction(const AddedFunctionPtr &addedFunc, + const AbstractMetaClassPtr &metaClass, + QString *errorMessage) +{ + AbstractMetaFunction *metaFunction = + traverseAddedFunctionHelper(addedFunc, metaClass, errorMessage); + if (metaFunction == nullptr) + return false; + + const AbstractMetaArgumentList fargs = metaFunction->arguments(); + if (metaClass->isNamespace()) + metaFunction->setCppAttribute(FunctionAttribute::Static); + if (metaFunction->name() == metaClass->name()) { + metaFunction->setFunctionType(AbstractMetaFunction::ConstructorFunction); + if (fargs.size() == 1) { + const auto te = fargs.constFirst().type().typeEntry(); + if (te->isCustom()) + metaFunction->setExplicit(true); + if (te->name() == metaFunction->name()) + metaFunction->setFunctionType(AbstractMetaFunction::CopyConstructorFunction); + } + } + + metaFunction->setDeclaringClass(metaClass); + metaFunction->setImplementingClass(metaClass); + AbstractMetaClass::addFunction(metaClass, AbstractMetaFunctionCPtr(metaFunction)); + metaClass->setHasNonPrivateConstructor(true); + return true; +} + +void AbstractMetaBuilderPrivate::fixArgumentNames(AbstractMetaFunction *func, const FunctionModificationList &mods) +{ + AbstractMetaArgumentList &arguments = func->arguments(); + + for (const FunctionModification &mod : mods) { + for (const ArgumentModification &argMod : mod.argument_mods()) { + if (!argMod.renamedToName().isEmpty()) + arguments[argMod.index() - 1].setName(argMod.renamedToName(), false); + } + } + + for (qsizetype i = 0, size = arguments.size(); i < size; ++i) { + if (arguments.at(i).name().isEmpty()) + arguments[i].setName(u"arg__"_s + QString::number(i + 1), false); + } +} + +static QString functionSignature(const FunctionModelItem &functionItem) +{ + QStringList args; + const ArgumentList &arguments = functionItem->arguments(); + for (const ArgumentModelItem &arg : arguments) + args << arg->type().toString(); + return functionItem->name() + u'(' + args.join(u',') + u')'; +} + +static inline QString qualifiedFunctionSignatureWithType(const FunctionModelItem &functionItem, + const QString &className = QString()) +{ + QString result = functionItem->type().toString() + u' '; + if (!className.isEmpty()) + result += className + u"::"_s; + result += functionSignature(functionItem); + return result; +} +static inline AbstractMetaFunction::FunctionType functionTypeFromCodeModel(CodeModel::FunctionType ft) +{ + AbstractMetaFunction::FunctionType result = AbstractMetaFunction::NormalFunction; + switch (ft) { + case CodeModel::Constructor: + result = AbstractMetaFunction::ConstructorFunction; + break; + case CodeModel::CopyConstructor: + result = AbstractMetaFunction::CopyConstructorFunction; + break; + case CodeModel::MoveConstructor: + result = AbstractMetaFunction::MoveConstructorFunction; + break; + case CodeModel::Destructor: + result = AbstractMetaFunction::DestructorFunction; + break; + case CodeModel::AssignmentOperator: + result = AbstractMetaFunction::AssignmentOperatorFunction; + break; + case CodeModel::CallOperator: + result = AbstractMetaFunction::CallOperator; + break; + case CodeModel::ConversionOperator: + result = AbstractMetaFunction::ConversionOperator; + break; + case CodeModel::DereferenceOperator: + result = AbstractMetaFunction::DereferenceOperator; + break; + case CodeModel::ReferenceOperator: + result = AbstractMetaFunction::ReferenceOperator; + break; + case CodeModel::ArrowOperator: + result = AbstractMetaFunction::ArrowOperator; + break; + case CodeModel::ArithmeticOperator: + result = AbstractMetaFunction::ArithmeticOperator; + break; + case CodeModel::IncrementOperator: + result = AbstractMetaFunction::IncrementOperator; + break; + case CodeModel::DecrementOperator: + result = AbstractMetaFunction::DecrementOperator; + break; + case CodeModel::BitwiseOperator: + result = AbstractMetaFunction::BitwiseOperator; + break; + case CodeModel::LogicalOperator: + result = AbstractMetaFunction::LogicalOperator; + break; + case CodeModel::ShiftOperator: + result = AbstractMetaFunction::ShiftOperator; + break; + case CodeModel::SubscriptOperator: + result = AbstractMetaFunction::SubscriptOperator; + break; + case CodeModel::ComparisonOperator: + result = AbstractMetaFunction::ComparisonOperator; + break; + case CodeModel::Normal: + break; + case CodeModel::Signal: + result = AbstractMetaFunction::SignalFunction; + break; + case CodeModel::Slot: + result = AbstractMetaFunction::SlotFunction; + break; + } + return result; +} + +static AbstractMetaFunction::FunctionType functionTypeFromName(const QString &name) +{ + if (name == u"__getattro__") + return AbstractMetaFunction::GetAttroFunction; + if (name == u"__setattro__") + return AbstractMetaFunction::SetAttroFunction; + const auto typeOpt = _FunctionModelItem::functionTypeFromName(name); + if (typeOpt.has_value()) + return functionTypeFromCodeModel(typeOpt.value()); + return AbstractMetaFunction::NormalFunction; +} + +// Apply the <array> modifications of the arguments +static bool applyArrayArgumentModifications(const FunctionModificationList &functionMods, + AbstractMetaFunction *func, + QString *errorMessage) +{ + for (const FunctionModification &mod : functionMods) { + for (const ArgumentModification &argMod : mod.argument_mods()) { + if (argMod.isArray()) { + const int i = argMod.index() - 1; + if (i < 0 || i >= func->arguments().size()) { + *errorMessage = msgCannotSetArrayUsage(func->minimalSignature(), i, + u"Index out of range."_s); + return false; + } + auto t = func->arguments().at(i).type(); + if (!t.applyArrayModification(errorMessage)) { + *errorMessage = msgCannotSetArrayUsage(func->minimalSignature(), i, *errorMessage); + return false; + } + func->arguments()[i].setType(t); + } + } + } + return true; +} + +// Create the meta type for a view (std::string_view -> std::string) +static AbstractMetaType createViewOnType(const AbstractMetaType &metaType, + const TypeEntryCPtr &viewOnTypeEntry) +{ + auto result = metaType; + result.setTypeEntry(viewOnTypeEntry); + if (!metaType.isContainer() || !viewOnTypeEntry->isContainer()) + return result; + // For containers, when sth with several template parameters + // (std::span<T, int N>) is mapped onto a std::vector<T>, + // remove the superfluous template parameters and strip 'const'. + const auto vcte = std::static_pointer_cast<const ContainerTypeEntry>(viewOnTypeEntry); + const auto &instantiations = metaType.instantiations(); + AbstractMetaTypeList viewInstantiations; + const auto size = std::min(vcte->templateParameterCount(), instantiations.size()); + for (qsizetype i = 0; i < size; ++i) { + auto ins = instantiations.at(i); + ins.setConstant(false); + viewInstantiations.append(ins); + } + result.setInstantiations(viewInstantiations); + return result; +} + +void AbstractMetaBuilderPrivate::rejectFunction(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass, + AbstractMetaBuilder::RejectReason reason, + const QString &rejectReason) +{ + QString sortKey; + if (currentClass) + sortKey += currentClass->typeEntry()->qualifiedCppName() + u"::"_s; + sortKey += functionSignature(functionItem); // Sort without return type + const QString signatureWithType = functionItem->type().toString() + u' ' + sortKey; + m_rejectedFunctions.insert({reason, signatureWithType, sortKey, rejectReason}); +} + +AbstractMetaFunction *AbstractMetaBuilderPrivate::traverseFunction(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass) +{ + const auto *tdb = TypeDatabase::instance(); + + if (!functionItem->templateParameters().isEmpty()) + return nullptr; + + if (functionItem->isDeleted()) { + switch (functionItem->functionType()) { + case CodeModel::Constructor: + if (functionItem->isDefaultConstructor()) + currentClass->setHasDeletedDefaultConstructor(true); + break; + case CodeModel::CopyConstructor: + currentClass->setHasDeletedCopyConstructor(true); + break; + default: + break; + } + return nullptr; + } + const QString &functionName = functionItem->name(); + const QString className = currentClass != nullptr ? + currentClass->typeEntry()->qualifiedCppName() : QString{}; + + if (m_apiExtractorFlags.testFlag(ApiExtractorFlag::UsePySideExtensions)) { + // Skip enum helpers generated by Q_ENUM + if ((currentClass == nullptr || currentClass->isNamespace()) + && (functionName == u"qt_getEnumMetaObject" || functionName == u"qt_getEnumName")) { + return nullptr; + } + + // Clang: Skip qt_metacast(), qt_metacall(), expanded from Q_OBJECT + // and overridden metaObject(), QGADGET helpers + if (currentClass != nullptr) { + if (functionName == u"qt_check_for_QGADGET_macro" + || functionName.startsWith(u"qt_meta")) { + return nullptr; + } + if (functionName == u"metaObject" && className != u"QObject") + return nullptr; + } + } // PySide extensions + + QString rejectReason; + if (tdb->isFunctionRejected(className, functionName, &rejectReason)) { + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::GenerationDisabled, rejectReason); + return nullptr; + } + + const QString &signature = functionSignature(functionItem); + if (tdb->isFunctionRejected(className, signature, &rejectReason)) { + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::GenerationDisabled, rejectReason); + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) { + qCInfo(lcShiboken, "%s::%s was rejected by the type database (%s).", + qPrintable(className), qPrintable(signature), qPrintable(rejectReason)); + } + return nullptr; + } + + if (functionItem->isFriend()) + return nullptr; + + const auto cppAttributes = functionItem->attributes(); + const bool deprecated = cppAttributes.testFlag(FunctionAttribute::Deprecated); + if (deprecated && m_skipDeprecated) { + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::GenerationDisabled, u" is deprecated."_s); + return nullptr; + } + + AbstractMetaFunction::Flags flags; + auto *metaFunction = new AbstractMetaFunction(functionName); + metaFunction->setCppAttributes(cppAttributes); + const QByteArray cSignature = signature.toUtf8(); + const QString unresolvedSignature = + QString::fromUtf8(QMetaObject::normalizedSignature(cSignature.constData())); + metaFunction->setUnresolvedSignature(unresolvedSignature); + if (functionItem->isHiddenFriend()) + flags.setFlag(AbstractMetaFunction::Flag::HiddenFriend); + metaFunction->setSourceLocation(functionItem->sourceLocation()); + + // Additional check for assignment/move assignment down below + metaFunction->setFunctionType(functionTypeFromCodeModel(functionItem->functionType())); + metaFunction->setConstant(functionItem->isConstant()); + metaFunction->setExceptionSpecification(functionItem->exceptionSpecification()); + + // Access rights + metaFunction->setAccess(functionItem->accessPolicy()); + + QString errorMessage; + switch (metaFunction->functionType()) { + case AbstractMetaFunction::DestructorFunction: + metaFunction->setType(AbstractMetaType::createVoid()); + break; + case AbstractMetaFunction::ConstructorFunction: + metaFunction->setName(currentClass->name()); + metaFunction->setType(AbstractMetaType::createVoid()); + break; + default: { + TypeInfo returnType = functionItem->type(); + + if (tdb->isReturnTypeRejected(className, returnType.toString(), &rejectReason)) { + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::GenerationDisabled, rejectReason); + delete metaFunction; + return nullptr; + } + + TranslateTypeFlags flags; + if (functionItem->scopeResolution()) + flags.setFlag(AbstractMetaBuilder::NoClassScopeLookup); + auto type = translateType(returnType, currentClass, flags, &errorMessage); + if (!type.has_value()) { + const QString reason = msgUnmatchedReturnType(functionItem, errorMessage); + const QString signature = qualifiedFunctionSignatureWithType(functionItem, className); + qCWarning(lcShiboken, "%s", + qPrintable(msgSkippingFunction(functionItem, signature, reason))); + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::UnmatchedReturnType, reason); + delete metaFunction; + return nullptr; + } + + metaFunction->setType(type.value()); + } + break; + } + + ArgumentList arguments = functionItem->arguments(); + // Add private signals for documentation purposes + if (!arguments.isEmpty() + && m_apiExtractorFlags.testFlag(ApiExtractorFlag::UsePySideExtensions) + && functionItem->functionType() == CodeModel::Signal + && arguments.constLast()->type().qualifiedName().constLast() == u"QPrivateSignal") { + flags.setFlag(AbstractMetaFunction::Flag::PrivateSignal); + arguments.removeLast(); + } + + if (arguments.size() == 1) { + ArgumentModelItem arg = arguments.at(0); + TypeInfo type = arg->type(); + if (type.qualifiedName().constFirst() == u"void" && type.indirections() == 0) + arguments.pop_front(); + } + + for (qsizetype i = 0; i < arguments.size(); ++i) { + const ArgumentModelItem &arg = arguments.at(i); + + if (tdb->isArgumentTypeRejected(className, arg->type().toString(), &rejectReason)) { + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::GenerationDisabled, rejectReason); + delete metaFunction; + return nullptr; + } + + TranslateTypeFlags flags; + if (arg->scopeResolution()) + flags.setFlag(AbstractMetaBuilder::NoClassScopeLookup); + auto metaTypeO = translateType(arg->type(), currentClass, flags, &errorMessage); + if (!metaTypeO.has_value()) { + // If an invalid argument has a default value, simply remove it + // unless the function is virtual (since the override in the + // wrapper can then not correctly be generated). + if (arg->defaultValue() + && !functionItem->attributes().testFlag(FunctionAttribute::Virtual)) { + if (!currentClass || currentClass->typeEntry()->generateCode()) { + const QString signature = qualifiedFunctionSignatureWithType(functionItem, className); + qCWarning(lcShiboken, "%s", + qPrintable(msgStrippingArgument(functionItem, i, signature, + arg, errorMessage))); + } + break; + } + const QString reason = msgUnmatchedParameterType(arg, i, errorMessage); + const QString signature = qualifiedFunctionSignatureWithType(functionItem, className); + qCWarning(lcShiboken, "%s", + qPrintable(msgSkippingFunction(functionItem, signature, reason))); + rejectFunction(functionItem, currentClass, + AbstractMetaBuilder::UnmatchedArgumentType, reason); + delete metaFunction; + return nullptr; + } + + auto metaType = metaTypeO.value(); + // Add view substitution for simple view types of function arguments + // std::string_view -> std::string for foo(std::string_view) + auto viewOnTypeEntry = metaType.typeEntry()->viewOn(); + if (viewOnTypeEntry != nullptr && metaType.indirections() == 0 + && metaType.arrayElementType() == nullptr + && (!metaType.hasInstantiations() || metaType.isContainer())) { + metaType.setViewOn(createViewOnType(metaType, viewOnTypeEntry)); + } + + AbstractMetaArgument metaArgument; + metaArgument.setType(metaType); + metaArgument.setName(arg->name()); + metaArgument.setArgumentIndex(i); + metaFunction->addArgument(metaArgument); + } + + AbstractMetaArgumentList &metaArguments = metaFunction->arguments(); + + const FunctionModificationList functionMods = currentClass + ? AbstractMetaFunction::findClassModifications(metaFunction, currentClass) + : AbstractMetaFunction::findGlobalModifications(metaFunction); + + applyCachedFunctionModifications(metaFunction, functionMods); + + // Find the correct default values + for (qsizetype i = 0, size = metaArguments.size(); i < size; ++i) { + const ArgumentModelItem &arg = arguments.at(i); + AbstractMetaArgument &metaArg = metaArguments[i]; + + const QString originalDefaultExpression = + fixDefaultValue(arg->defaultValueExpression(), metaArg.type(), currentClass); + + metaArg.setOriginalDefaultValueExpression(originalDefaultExpression); + metaArg.setDefaultValueExpression(originalDefaultExpression); + + applyDefaultExpressionModifications(functionMods, i, &metaArg); + + //Check for missing argument name + if (!metaArg.defaultValueExpression().isEmpty() + && !metaArg.hasName() + && !metaFunction->isOperatorOverload() + && !metaFunction->isSignal() + && metaFunction->argumentName(i + 1, false, currentClass).isEmpty()) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnnamedArgumentDefaultExpression(currentClass, i + 1, + className, metaFunction))); + } + + } + + if (!metaArguments.isEmpty()) { + fixArgumentNames(metaFunction, functionMods); + QString errorMessage; + if (!applyArrayArgumentModifications(functionMods, metaFunction, &errorMessage)) { + qCWarning(lcShiboken, "%s", + qPrintable(msgArrayModificationFailed(functionItem, className, errorMessage))); + } + } + + // Determine class special functions + if (currentClass && metaFunction->arguments().size() == 1) { + const AbstractMetaType &argType = metaFunction->arguments().constFirst().type(); + if (argType.typeEntry() == currentClass->typeEntry() && argType.indirections() == 0) { + if (metaFunction->name() == u"operator=") { + switch (argType.referenceType()) { + case NoReference: + metaFunction->setFunctionType(AbstractMetaFunction::AssignmentOperatorFunction); + break; + case LValueReference: + if (argType.isConstant()) + metaFunction->setFunctionType(AbstractMetaFunction::AssignmentOperatorFunction); + break; + case RValueReference: + metaFunction->setFunctionType(AbstractMetaFunction::MoveAssignmentOperatorFunction); + break; + } + } + } + } + metaFunction->setFlags(flags); + return metaFunction; +} + +static TypeEntryCPtr findTypeEntryUsingContext(const AbstractMetaClassCPtr &metaClass, + const QString& qualifiedName) +{ + TypeEntryCPtr type; + QStringList context = metaClass->qualifiedCppName().split(u"::"_s); + while (!type && !context.isEmpty()) { + type = TypeDatabase::instance()->findType(context.join(u"::"_s) + u"::"_s + qualifiedName); + context.removeLast(); + } + return type; +} + +// Helper for findTypeEntries/translateTypeStatic() +TypeEntryCList AbstractMetaBuilderPrivate::findTypeEntriesHelper(const QString &qualifiedName, + const QString &name, + TranslateTypeFlags flags, + const AbstractMetaClassCPtr ¤tClass, + AbstractMetaBuilderPrivate *d) +{ + // 5.1 - Try first using the current scope + if (currentClass != nullptr + && !flags.testFlag(AbstractMetaBuilder::NoClassScopeLookup)) { + if (auto type = findTypeEntryUsingContext(currentClass, qualifiedName)) + return {type}; + + // 5.1.1 - Try using the class parents' scopes + if (d && !currentClass->baseClassNames().isEmpty()) { + const auto &baseClasses = d->getBaseClasses(currentClass); + for (const auto &cls : baseClasses) { + if (auto type = findTypeEntryUsingContext(cls, qualifiedName)) + return {type}; + } + } + } + + // 5.2 - Try without scope + auto types = TypeDatabase::instance()->findCppTypes(qualifiedName); + if (!types.isEmpty()) + return types; + + // 6. No? Try looking it up as a flags type + if (auto type = TypeDatabase::instance()->findFlagsType(qualifiedName)) + return {type}; + + // 7. No? Try looking it up as a container type + if (auto type = TypeDatabase::instance()->findContainerType(name)) + return {type}; + + // 8. No? Check if the current class is a template and this type is one + // of the parameters. + if (currentClass) { + const auto &template_args = currentClass->templateArguments(); + for (const auto &te : template_args) { + if (te->name() == qualifiedName) + return {te}; + } + } + return {}; +} + +// Helper for translateTypeStatic() that calls findTypeEntriesHelper() +// and does some error checking. +TypeEntryCList AbstractMetaBuilderPrivate::findTypeEntries(const QString &qualifiedName, + const QString &name, + TranslateTypeFlags flags, + const AbstractMetaClassCPtr ¤tClass, + AbstractMetaBuilderPrivate *d, + QString *errorMessage) +{ + TypeEntryCList types = findTypeEntriesHelper(qualifiedName, name, flags, + currentClass, d); + if (types.isEmpty()) { + if (errorMessage != nullptr) + *errorMessage = msgCannotFindTypeEntry(qualifiedName); + return {}; + } + + // Resolve entries added by metabuilder (for example, "GLenum") to match + // the signatures for modifications. + for (qsizetype i = 0, size = types.size(); i < size; ++i) { + const auto &e = types.at(i); + if (e->isPrimitive()) { + const auto pte = std::static_pointer_cast<const PrimitiveTypeEntry>(e); + types[i] = basicReferencedNonBuiltinTypeEntry(pte); + } + } + + if (types.size() == 1) + return types; + + const auto typeEntryType = types.constFirst()->type(); + const bool sameType = std::all_of(types.cbegin() + 1, types.cend(), + [typeEntryType](const TypeEntryCPtr &e) { + return e->type() == typeEntryType; + }); + + if (!sameType) { + if (errorMessage != nullptr) + *errorMessage = msgAmbiguousVaryingTypesFound(qualifiedName, types); + return {}; + } + // Ambiguous primitive/smart pointer types are possible (when + // including type systems). + if (typeEntryType != TypeEntry::PrimitiveType + && typeEntryType != TypeEntry::SmartPointerType) { + if (errorMessage != nullptr) + *errorMessage = msgAmbiguousTypesFound(qualifiedName, types); + return {}; + } + return types; +} + +// Reverse lookup of AbstractMetaType representing a template specialization +// found during traversing function arguments to its type system typedef'ed +// class. +AbstractMetaClassCPtr AbstractMetaBuilderPrivate::resolveTypeSystemTypeDef(const AbstractMetaType &t) const +{ + if (t.hasInstantiations()) { + auto pred = [t](const TypeClassEntry &e) { return e.type == t; }; + auto it = std::find_if(m_typeSystemTypeDefs.cbegin(), m_typeSystemTypeDefs.cend(), pred); + if (it != m_typeSystemTypeDefs.cend()) + return it->klass; + } + return nullptr; +} + +// The below helpers and AbstractMetaBuilderPrivate::fixSmartPointers() +// synthesize missing smart pointer functions and classes. For example for +// std::shared_ptr, the full class declaration or base classes from +// internal, compiler-dependent STL implementation headers might not be exposed +// to the parser unless those headers are specified as <system-include>. + +static void synthesizeWarning(const AbstractMetaFunctionCPtr &f) +{ + qCWarning(lcShiboken, "Synthesizing \"%s\"...", + qPrintable(f->classQualifiedSignature())); +} + +static AbstractMetaFunctionPtr + addMethod(const AbstractMetaClassPtr &s, const AbstractMetaType &returnType, + const QString &name, bool isConst = true) +{ + auto function = std::make_shared<AbstractMetaFunction>(name); + function->setType(returnType); + AbstractMetaClass::addFunction(s, function); + function->setConstant(isConst); + synthesizeWarning(function); + return function; +} + +static AbstractMetaFunctionPtr + addMethod(const AbstractMetaClassPtr &s, const QString &returnTypeName, + const QString &name, bool isConst = true) +{ + auto typeEntry = TypeDatabase::instance()->findPrimitiveType(returnTypeName); + Q_ASSERT(typeEntry); + AbstractMetaType returnType(typeEntry); + returnType.decideUsagePattern(); + return addMethod(s, returnType, name, isConst); +} + +// Create the instantiation type of a smart pointer +static AbstractMetaType instantiationType(const AbstractMetaClassCPtr &s, + const SmartPointerTypeEntryCPtr &ste) +{ + AbstractMetaType type(s->templateArguments().constFirst()); + if (ste->smartPointerType() != TypeSystem::SmartPointerType::ValueHandle) + type.addIndirection(); + type.decideUsagePattern(); + return type; +} + +// Create the pointee argument of a smart pointer constructor or reset() +static AbstractMetaArgument pointeeArgument(const AbstractMetaClassCPtr &s, + const SmartPointerTypeEntryCPtr &ste) +{ + AbstractMetaArgument pointee; + pointee.setType(instantiationType(s, ste)); + pointee.setName(u"pointee"_s); + return pointee; +} + +// Add the smart pointer constructors. For MSVC, (when not specifying +// <system-header>), clang only sees the default constructor. +static void fixSmartPointerConstructors(const AbstractMetaClassPtr &s, + const SmartPointerTypeEntryCPtr &ste) +{ + const auto ctors = s->queryFunctions(FunctionQueryOption::Constructors); + bool seenDefaultConstructor = false; + bool seenParameter = false; + for (const auto &ctor : ctors) { + if (ctor->arguments().isEmpty()) + seenDefaultConstructor = true; + else + seenParameter = true; + } + + if (!seenParameter) { + auto constructor = std::make_shared<AbstractMetaFunction>(s->name()); + constructor->setFunctionType(AbstractMetaFunction::ConstructorFunction); + constructor->addArgument(pointeeArgument(s, ste)); + AbstractMetaClass::addFunction(s, constructor); + synthesizeWarning(constructor); + } + + if (!seenDefaultConstructor) { + auto constructor = std::make_shared<AbstractMetaFunction>(s->name()); + constructor->setFunctionType(AbstractMetaFunction::ConstructorFunction); + AbstractMetaClass::addFunction(s, constructor); + synthesizeWarning(constructor); + } +} + +// Similarly, add the smart pointer reset() functions +static void fixSmartPointerReset(const AbstractMetaClassPtr &s, + const SmartPointerTypeEntryCPtr &ste) +{ + const QString resetMethodName = ste->resetMethod(); + const auto functions = s->findFunctions(resetMethodName); + bool seenParameterLess = false; + bool seenParameter = false; + for (const auto &function : functions) { + if (function->arguments().isEmpty()) + seenParameterLess = true; + else + seenParameter = true; + } + + if (!seenParameter) { + auto f = std::make_shared<AbstractMetaFunction>(resetMethodName); + f->addArgument(pointeeArgument(s, ste)); + AbstractMetaClass::addFunction(s, f); + synthesizeWarning(f); + } + + if (!seenParameterLess) { + auto f = std::make_shared<AbstractMetaFunction>(resetMethodName); + AbstractMetaClass::addFunction(s, f); + synthesizeWarning(f); + } +} + +// Add the relevant missing smart pointer functions. +static void fixSmartPointerClass(const AbstractMetaClassPtr &s, + const SmartPointerTypeEntryCPtr &ste) +{ + fixSmartPointerConstructors(s, ste); + + if (!ste->resetMethod().isEmpty()) + fixSmartPointerReset(s, ste); + + const QString getterName = ste->getter(); + if (!s->findFunction(getterName)) + addMethod(s, instantiationType(s, ste), getterName); + + const QString refCountName = ste->refCountMethodName(); + if (!refCountName.isEmpty() && !s->findFunction(refCountName)) + addMethod(s, u"int"_s, refCountName); + + const QString valueCheckMethod = ste->valueCheckMethod(); + if (!valueCheckMethod.isEmpty() && !s->findFunction(valueCheckMethod)) { + auto f = addMethod(s, u"bool"_s, valueCheckMethod); + if (valueCheckMethod == u"operator bool") + f->setFunctionType(AbstractMetaFunction::ConversionOperator); + } + + const QString nullCheckMethod = ste->nullCheckMethod(); + if (!nullCheckMethod.isEmpty() && !s->findFunction(nullCheckMethod)) + addMethod(s, u"bool"_s, nullCheckMethod); +} + +// Create a missing smart pointer class +static AbstractMetaClassPtr createSmartPointerClass(const SmartPointerTypeEntryCPtr &ste, + const AbstractMetaClassList &allClasses) +{ + auto result = std::make_shared<AbstractMetaClass>(); + result->setTypeEntry(std::const_pointer_cast<SmartPointerTypeEntry>(ste)); + auto templateArg = std::make_shared<TemplateArgumentEntry>(u"T"_s, ste->version(), + typeSystemTypeEntry(ste)); + result->setTemplateArguments({templateArg}); + fixSmartPointerClass(result, ste); + auto enclosingTe = ste->parent(); + if (!enclosingTe->isTypeSystem()) { + const auto enclosing = AbstractMetaClass::findClass(allClasses, enclosingTe); + if (!enclosing) + throw Exception(msgEnclosingClassNotFound(ste)); + result->setEnclosingClass(enclosing); + auto inner = enclosing->innerClasses(); + inner.append(std::const_pointer_cast<const AbstractMetaClass>(result)); + enclosing->setInnerClasses(inner); + } + return result; +} + +void AbstractMetaBuilderPrivate::fixSmartPointers() +{ + const auto smartPointerTypes = TypeDatabase::instance()->smartPointerTypes(); + for (const auto &ste : smartPointerTypes) { + const auto smartPointerClass = + AbstractMetaClass::findClass(m_smartPointers, ste); + if (smartPointerClass) { + fixSmartPointerClass(std::const_pointer_cast<AbstractMetaClass>(smartPointerClass), + ste); + } else { + qCWarning(lcShiboken, "Synthesizing smart pointer \"%s\"...", + qPrintable(ste->qualifiedCppName())); + m_smartPointers.append(createSmartPointerClass(ste, m_metaClasses)); + } + } +} + +std::optional<AbstractMetaType> + AbstractMetaBuilderPrivate::translateType(const TypeInfo &_typei, + const AbstractMetaClassCPtr ¤tClass, + TranslateTypeFlags flags, + QString *errorMessage) +{ + return translateTypeStatic(_typei, currentClass, this, flags, errorMessage); +} + +static bool isNumber(const QString &s) +{ + return std::all_of(s.cbegin(), s.cend(), + [](QChar c) { return c.isDigit(); }); +} + +// A type entry relevant only for non type template "X<5>" +static bool isNonTypeTemplateArgument(const TypeEntryCPtr &te) +{ + const auto type = te->type(); + return type == TypeEntry::EnumValue || type == TypeEntry::ConstantValueType; +} + +std::optional<AbstractMetaType> + AbstractMetaBuilderPrivate::translateTypeStatic(const TypeInfo &_typei, + const AbstractMetaClassCPtr ¤tClass, + AbstractMetaBuilderPrivate *d, + TranslateTypeFlags flags, + QString *errorMessageIn) +{ + if (_typei.isVoid()) + return AbstractMetaType::createVoid(); + + // 1. Test the type info without resolving typedefs in case this is present in the + // type system + const bool resolveType = !flags.testFlag(AbstractMetaBuilder::DontResolveType); + if (resolveType) { + auto resolved = + translateTypeStatic(_typei, currentClass, d, + flags | AbstractMetaBuilder::DontResolveType, + errorMessageIn); + if (resolved.has_value()) + return resolved; + } + + TypeInfo typeInfo = _typei; + if (resolveType) { + // Go through all parts of the current scope (including global namespace) + // to resolve typedefs. The parser does not properly resolve typedefs in + // the global scope when they are referenced from inside a namespace. + // This is a work around to fix this bug since fixing it in resolveType + // seemed non-trivial + qsizetype i = d ? d->m_scopes.size() - 1 : -1; + while (i >= 0) { + typeInfo = TypeInfo::resolveType(_typei, d->m_scopes.at(i--)); + if (typeInfo.qualifiedName().join(u"::"_s) != _typei.qualifiedName().join(u"::"_s)) + break; + } + + } + + if (typeInfo.isFunctionPointer()) { + if (errorMessageIn) + *errorMessageIn = msgUnableToTranslateType(_typei, u"Unsupported function pointer."_s); + return {}; + } + + QString errorMessage; + + // 2. Handle arrays. + // 2.1 Handle char arrays with unspecified size (aka "const char[]") as "const char*" with + // NativePointerPattern usage. + bool oneDimensionalArrayOfUnspecifiedSize = + typeInfo.arrayElements().size() == 1 + && typeInfo.arrayElements().at(0).isEmpty(); + + bool isConstCharStarCase = + oneDimensionalArrayOfUnspecifiedSize + && typeInfo.qualifiedName().size() == 1 + && typeInfo.qualifiedName().at(0) == "char"_L1 + && typeInfo.indirections() == 0 + && typeInfo.isConstant() + && typeInfo.referenceType() == NoReference + && typeInfo.arguments().isEmpty(); + + if (isConstCharStarCase) + typeInfo.setIndirections(typeInfo.indirections() + typeInfo.arrayElements().size()); + + // 2.2 Handle regular arrays. + if (!typeInfo.arrayElements().isEmpty() && !isConstCharStarCase) { + TypeInfo newInfo; + //newInfo.setArguments(typeInfo.arguments()); + newInfo.setIndirectionsV(typeInfo.indirectionsV()); + newInfo.setConstant(typeInfo.isConstant()); + newInfo.setVolatile(typeInfo.isVolatile()); + newInfo.setFunctionPointer(typeInfo.isFunctionPointer()); + newInfo.setQualifiedName(typeInfo.qualifiedName()); + newInfo.setReferenceType(typeInfo.referenceType()); + newInfo.setVolatile(typeInfo.isVolatile()); + + auto elementType = translateTypeStatic(newInfo, currentClass, d, flags, &errorMessage); + if (!elementType.has_value()) { + if (errorMessageIn) { + errorMessage.prepend(u"Unable to translate array element: "_s); + *errorMessageIn = msgUnableToTranslateType(_typei, errorMessage); + } + return {}; + } + + for (auto i = typeInfo.arrayElements().size() - 1; i >= 0; --i) { + AbstractMetaType arrayType; + arrayType.setArrayElementType(elementType.value()); + const QString &arrayElement = typeInfo.arrayElements().at(i); + if (!arrayElement.isEmpty()) { + bool _ok; + const qint64 elems = d + ? d->findOutValueFromString(arrayElement, _ok) + : arrayElement.toLongLong(&_ok, 0); + if (_ok) + arrayType.setArrayElementCount(int(elems)); + } + auto elementTypeEntry = elementType->typeEntry(); + auto at = std::make_shared<ArrayTypeEntry>(elementTypeEntry, elementTypeEntry->version(), + elementTypeEntry->parent()); + arrayType.setTypeEntry(at); + arrayType.decideUsagePattern(); + + elementType = arrayType; + } + + return elementType; + } + + QStringList qualifierList = typeInfo.qualifiedName(); + if (qualifierList.isEmpty()) { + errorMessage = msgUnableToTranslateType(_typei, u"horribly broken type"_s); + if (errorMessageIn) + *errorMessageIn = errorMessage; + else + qCWarning(lcShiboken,"%s", qPrintable(errorMessage)); + return {}; + } + + QString qualifiedName = qualifierList.join(u"::"_s); + QString name = qualifierList.takeLast(); + + // 4. Special case QFlags (include instantiation in name) + if (qualifiedName == u"QFlags") { + qualifiedName = typeInfo.toString(); + typeInfo.clearInstantiations(); + } + + TypeEntryCList types = findTypeEntries(qualifiedName, name, flags, + currentClass, d, errorMessageIn); + if (!flags.testFlag(AbstractMetaBuilder::TemplateArgument)) { + // Avoid clashes between QByteArray and enum value QMetaType::QByteArray + // unless we are looking for template arguments. + auto end = std::remove_if(types.begin(), types.end(), + isNonTypeTemplateArgument); + types.erase(end, types.end()); + } + + if (types.isEmpty()) { + if (errorMessageIn != nullptr) + *errorMessageIn = msgUnableToTranslateType(_typei, *errorMessageIn); + return {}; + } + + TypeEntryCPtr type = types.constFirst(); + const TypeEntry::Type typeEntryType = type->type(); + + AbstractMetaType metaType; + metaType.setIndirectionsV(typeInfo.indirectionsV()); + metaType.setReferenceType(typeInfo.referenceType()); + metaType.setConstant(typeInfo.isConstant()); + metaType.setVolatile(typeInfo.isVolatile()); + metaType.setOriginalTypeDescription(_typei.toString()); + + const auto &templateArguments = typeInfo.instantiations(); + for (qsizetype t = 0, size = templateArguments.size(); t < size; ++t) { + const TypeInfo &ti = templateArguments.at(t); + auto targType = translateTypeStatic(ti, currentClass, d, + flags | AbstractMetaBuilder::TemplateArgument, + &errorMessage); + // For non-type template parameters, create a dummy type entry on the fly + // as is done for classes. + if (!targType.has_value()) { + const QString value = ti.qualifiedName().join(u"::"_s); + if (isNumber(value)) { + auto module = typeSystemTypeEntry(type); + TypeDatabase::instance()->addConstantValueTypeEntry(value, module); + targType = translateTypeStatic(ti, currentClass, d, flags, &errorMessage); + } + } + if (!targType) { + if (errorMessageIn) + *errorMessageIn = msgCannotTranslateTemplateArgument(t, ti, errorMessage); + return {}; + } + + metaType.addInstantiation(targType.value()); + } + + if (typeEntryType == TypeEntry::SmartPointerType) { + // Find a matching instantiation + if (metaType.instantiations().size() != 1) { + if (errorMessageIn) + *errorMessageIn = msgInvalidSmartPointerType(_typei); + return {}; + } + auto instantiationType = metaType.instantiations().constFirst().typeEntry(); + if (instantiationType->type() == TypeEntry::TemplateArgumentType) { + // Member functions of the template itself, SharedPtr(const SharedPtr &) + type = instantiationType; + } else { + auto it = std::find_if(types.cbegin(), types.cend(), + [instantiationType](const TypeEntryCPtr &e) { + auto smartPtr = std::static_pointer_cast<const SmartPointerTypeEntry>(e); + return smartPtr->matchesInstantiation(instantiationType); + }); + if (it == types.cend()) { + if (errorMessageIn) + *errorMessageIn = msgCannotFindSmartPointerInstantion(_typei); + return {}; + } + type =*it; + } + } + + metaType.setTypeEntry(type); + + // The usage pattern *must* be decided *after* the possible template + // instantiations have been determined, or else the absence of + // such instantiations will break the caching scheme of + // AbstractMetaType::cppSignature(). + metaType.decideUsagePattern(); + + if (d) { + // Reverse lookup of type system typedefs. Replace by class. + if (auto klass = d->resolveTypeSystemTypeDef(metaType)) { + metaType = AbstractMetaType{}; + metaType.setTypeEntry(klass->typeEntry()); + metaType.decideUsagePattern(); + } + } + + return metaType; +} + +std::optional<AbstractMetaType> + AbstractMetaBuilder::translateType(const TypeInfo &_typei, + const AbstractMetaClassPtr ¤tClass, + TranslateTypeFlags flags, + QString *errorMessage) +{ + return AbstractMetaBuilderPrivate::translateTypeStatic(_typei, currentClass, + nullptr, flags, + errorMessage); +} + +std::optional<AbstractMetaType> + AbstractMetaBuilder::translateType(const QString &t, + const AbstractMetaClassPtr ¤tClass, + TranslateTypeFlags flags, + QString *errorMessageIn) +{ + QString errorMessage; + TypeInfo typeInfo = TypeParser::parse(t, &errorMessage); + if (typeInfo.qualifiedName().isEmpty()) { + errorMessage = msgUnableToTranslateType(t, errorMessage); + if (errorMessageIn) + *errorMessageIn = errorMessage; + else + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return {}; + } + return translateType(typeInfo, currentClass, flags, errorMessageIn); +} + +qint64 AbstractMetaBuilderPrivate::findOutValueFromString(const QString &stringValue, bool &ok) +{ + qint64 value = stringValue.toLongLong(&ok); + if (ok) + return value; + + if (stringValue == u"true" || stringValue == u"false") { + ok = true; + return (stringValue == u"true"); + } + + // This is a very lame way to handle expression evaluation, + // but it is not critical and will do for the time being. + static const QRegularExpression variableNameRegExp("^[a-zA-Z_][a-zA-Z0-9_]*$"_L1); + Q_ASSERT(variableNameRegExp.isValid()); + if (!variableNameRegExp.match(stringValue).hasMatch()) { + ok = true; + return 0; + } + + auto enumValue = AbstractMetaClass::findEnumValue(m_metaClasses, stringValue); + if (enumValue.has_value()) { + ok = true; + return enumValue->value().value(); + } + + for (const AbstractMetaEnum &metaEnum : std::as_const(m_globalEnums)) { + auto ev = metaEnum.findEnumValue(stringValue); + if (ev.has_value()) { + ok = true; + return ev->value().value(); + } + } + + ok = false; + return 0; +} + +// Return whether candidate is some underqualified specification of qualifiedType +// ("B::C" should be qualified to "A::B::C") +static bool isUnderQualifiedSpec(QStringView qualifiedType, QStringView candidate) +{ + const auto candidateSize = candidate.size(); + const auto qualifiedTypeSize = qualifiedType.size(); + return candidateSize < qualifiedTypeSize + && qualifiedType.endsWith(candidate) + && qualifiedType.at(qualifiedTypeSize - candidateSize - 1) == u':'; +} + +QString AbstractMetaBuilder::fixEnumDefault(const AbstractMetaType &type, + const QString &expr, + const AbstractMetaClassCPtr &klass) const +{ + return d->fixEnumDefault(type, expr, klass); +} + +void AbstractMetaBuilder::setCodeModelTestMode(bool b) +{ + AbstractMetaBuilderPrivate::m_codeModelTestMode = b; +} + +// Helper to fix a simple default value (field or enum reference) in a +// class context. +QString AbstractMetaBuilderPrivate::fixSimpleDefaultValue(QStringView expr, + const AbstractMetaClassCPtr &klass) const +{ + const QString field = qualifyStaticField(klass, expr); + + if (!field.isEmpty()) + return field; + const auto cit = m_classToItem.constFind(klass); + if (cit == m_classToItem.cend()) + return {}; + auto *scope = dynamic_cast<const _ScopeModelItem *>(cit.value()); + if (!scope) + return {}; + if (auto enumValue = scope->findEnumByValue(expr)) + return enumValue.qualifiedName; + return {}; +} + +// see TestResolveType::testFixDefaultArguments() +QString AbstractMetaBuilderPrivate::fixDefaultValue(QString expr, const AbstractMetaType &type, + const AbstractMetaClassCPtr &implementingClass) const +{ + expr.replace(u'\n', u' '); // breaks signature parser + + if (AbstractMetaBuilder::dontFixDefaultValue(expr)) + return expr; + + if (type.isFlags() || type.isEnum()) { + expr = fixEnumDefault(type, expr, implementingClass); + } else if (type.isContainer() && expr.contains(u'<')) { + // Expand a container of a nested class, fex + // "QList<FormatRange>()" -> "QList<QTextLayout::FormatRange>()" + if (type.instantiations().size() != 1) + return expr; // Only simple types are handled, not QMap<int, int>. + auto innerTypeEntry = type.instantiations().constFirst().typeEntry(); + if (!innerTypeEntry->isComplex()) + return expr; + const QString &qualifiedInnerTypeName = innerTypeEntry->qualifiedCppName(); + if (!qualifiedInnerTypeName.contains(u"::")) // Nothing to qualify here + return expr; + const auto openPos = expr.indexOf(u'<'); + const auto closingPos = expr.lastIndexOf(u'>'); + if (openPos == -1 || closingPos == -1) + return expr; + const auto innerPos = openPos + 1; + const auto innerLen = closingPos - innerPos; + const auto innerType = QStringView{expr}.mid(innerPos, innerLen).trimmed(); + if (isUnderQualifiedSpec(qualifiedInnerTypeName, innerType)) + expr.replace(innerPos, innerLen, qualifiedInnerTypeName); + } else { + // Here the default value is supposed to be a constructor, a class field, + // a constructor receiving a static class field or an enum. Consider + // class QSqlDatabase { ... + // static const char *defaultConnection; + // QSqlDatabase(const QString &connection = QLatin1String(defaultConnection)) + // -> = QLatin1String(QSqlDatabase::defaultConnection) + // static void foo(QSqlDatabase db = QSqlDatabase(defaultConnection)); + // -> = QSqlDatabase(QSqlDatabase::defaultConnection) + // + // Enum values from the class as defaults of int and others types (via + // implicit conversion) are handled here as well: + // class QStyleOption { ... + // enum StyleOptionType { Type = SO_Default }; + // QStyleOption(..., int type = SO_Default); + // -> = QStyleOption::StyleOptionType::SO_Default + + // Is this a single field or an enum? + if (isQualifiedCppIdentifier(expr)) { + const QString fixed = fixSimpleDefaultValue(expr, implementingClass); + return fixed.isEmpty() ? expr : fixed; + } + + // Is this sth like "QLatin1String(field)", "Class(Field)", "Class()"? + const auto parenPos = expr.indexOf(u'('); + if (parenPos == -1 || !expr.endsWith(u')')) + return expr; + // Is the term within parentheses a class field or enum? + const auto innerLength = expr.size() - parenPos - 2; + if (innerLength > 0) { // Not some function call "defaultFunc()" + const auto inner = QStringView{expr}.mid(parenPos + 1, innerLength); + if (isQualifiedCppIdentifier(inner) + && !AbstractMetaBuilder::dontFixDefaultValue(inner)) { + const QString replacement = fixSimpleDefaultValue(inner, implementingClass); + if (!replacement.isEmpty() && replacement != inner) + expr.replace(parenPos + 1, innerLength, replacement); + } + } + // Is this a class constructor "Class(Field)"? Expand it. + const auto te = type.typeEntry(); + if (!te->isComplex()) + return expr; + const QString &qualifiedTypeName = te->qualifiedCppName(); + if (!qualifiedTypeName.contains(u"::")) // Nothing to qualify here + return expr; + const auto className = QStringView{expr}.left(parenPos); + if (isUnderQualifiedSpec(qualifiedTypeName, className)) + expr.replace(0, className.size(), qualifiedTypeName); + } + + return expr; +} + +QString AbstractMetaBuilder::fixDefaultValue(const QString &expr, const AbstractMetaType &type, + const AbstractMetaClassCPtr &c) const +{ + return d->fixDefaultValue(expr, type, c); +} + +bool AbstractMetaBuilderPrivate::isEnum(const FileModelItem &dom, const QStringList& qualified_name) +{ + CodeModelItem item = dom->model()->findItem(qualified_name, dom); + return item && item->kind() == _EnumModelItem::__node_kind; +} + +AbstractMetaClassPtr + AbstractMetaBuilderPrivate::findTemplateClass(const QString &name, + const AbstractMetaClassCPtr &context, + TypeInfo *info, + ComplexTypeEntryPtr *baseContainerType) const +{ + if (baseContainerType) + baseContainerType->reset(); + auto *types = TypeDatabase::instance(); + + QStringList scope = context->typeEntry()->qualifiedCppName().split(u"::"_s); + QString errorMessage; + scope.removeLast(); + for (auto i = scope.size(); i >= 0; --i) { + QString prefix = i > 0 ? QStringList(scope.mid(0, i)).join(u"::"_s) + u"::"_s : QString(); + QString completeName = prefix + name; + const TypeInfo parsed = TypeParser::parse(completeName, &errorMessage); + QString qualifiedName = parsed.qualifiedName().join(u"::"_s); + if (qualifiedName.isEmpty()) { + qWarning().noquote().nospace() << "Unable to parse type \"" << completeName + << "\" while looking for template \"" << name << "\": " << errorMessage; + continue; + } + if (info) + *info = parsed; + + AbstractMetaClassPtr templ; + for (const auto &c : std::as_const(m_templates)) { + if (c->typeEntry()->name() == qualifiedName) { + templ = c; + break; + } + } + + if (!templ) + templ = AbstractMetaClass::findClass(m_metaClasses, qualifiedName); + + if (templ) + return templ; + + if (baseContainerType) + *baseContainerType = types->findContainerType(qualifiedName); + } + + return nullptr; +} + +AbstractMetaClassCList + AbstractMetaBuilderPrivate::getBaseClasses(const AbstractMetaClassCPtr &metaClass) const +{ + // Shortcut if inheritance has already been set up + if (metaClass->inheritanceDone() || !metaClass->needsInheritanceSetup()) + return metaClass->baseClasses(); + AbstractMetaClassCList baseClasses; + const QStringList &baseClassNames = metaClass->baseClassNames(); + for (const QString& parent : baseClassNames) { + const auto cls = parent.contains(u'<') + ? findTemplateClass(parent, metaClass) + : AbstractMetaClass::findClass(m_metaClasses, parent); + + if (cls) + baseClasses << cls; + } + return baseClasses; +} + +std::optional<AbstractMetaType> + AbstractMetaBuilderPrivate::inheritTemplateType(const AbstractMetaTypeList &templateTypes, + const AbstractMetaType &metaType) +{ + auto returned = metaType; + + if (!metaType.typeEntry()->isTemplateArgument() && !metaType.hasInstantiations()) + return returned; + + returned.setOriginalTemplateType(metaType); + + if (returned.typeEntry()->isTemplateArgument()) { + const auto tae = std::static_pointer_cast<const TemplateArgumentEntry>(returned.typeEntry()); + + // If the template is intantiated with void we special case this as rejecting the functions that use this + // parameter from the instantiation. + const AbstractMetaType &templateType = templateTypes.value(tae->ordinal()); + if (templateType.typeEntry()->isVoid()) + return {}; + + AbstractMetaType t = returned; + t.setTypeEntry(templateType.typeEntry()); + t.setIndirections(templateType.indirections() + t.indirections() ? 1 : 0); + t.decideUsagePattern(); + + return inheritTemplateType(templateTypes, t); + } + + if (returned.hasInstantiations()) { + AbstractMetaTypeList instantiations = returned.instantiations(); + for (qsizetype i = 0; i < instantiations.size(); ++i) { + auto ins = inheritTemplateType(templateTypes, instantiations.at(i)); + if (!ins.has_value()) + return {}; + instantiations[i] = ins.value(); + } + returned.setInstantiations(instantiations); + } + + return returned; +} + +AbstractMetaClassPtr + AbstractMetaBuilder::inheritTemplateClass(const ComplexTypeEntryPtr &te, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes) +{ + auto result = std::make_shared<AbstractMetaClass>(); + result->setTypeDef(true); + + result->setTypeEntry(te); + if (!AbstractMetaBuilderPrivate::inheritTemplate(result, templateClass, + templateTypes)) { + return {}; + } + AbstractMetaBuilderPrivate::inheritTemplateFunctions(result); + return result; +} + + +static std::optional<AbstractMetaType> + inheritTemplateParameter(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info, QString *errorMessage) +{ + QString typeName = info.qualifiedName().join("::"_L1); + TypeDatabase *typeDb = TypeDatabase::instance(); + TypeEntryPtr t; + // Check for a non-type template integer parameter, that is, for a base + // "template <int R, int C> Matrix<R, C>" and subclass + // "typedef Matrix<2,3> Matrix2x3;". If so, create dummy entries of + // EnumValueTypeEntry for the integer values encountered on the fly. + if (isNumber(typeName)) { + t = typeDb->findType(typeName); + if (!t) { + auto parent = typeSystemTypeEntry(subclass->typeEntry()); + t = TypeDatabase::instance()->addConstantValueTypeEntry(typeName, parent); + } + } else { + QStringList possibleNames; + possibleNames << subclass->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << templateClass->qualifiedCppName() + "::"_L1 + typeName; + if (subclass->enclosingClass()) + possibleNames << subclass->enclosingClass()->qualifiedCppName() + "::"_L1 + typeName; + possibleNames << typeName; + + for (const QString &possibleName : std::as_const(possibleNames)) { + t = typeDb->findType(possibleName); + if (t) + break; + } + } + + if (!t) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "The corresponding type was not found in the typesystem."); + return std::nullopt; + } + + if (t->isContainer()) { + *errorMessage = msgIgnoringTemplateParameter(typeName, + "Template inheritance from nested containers is not supported"); + return std::nullopt; + } + AbstractMetaType result(t); + result.setConstant(info.isConstant()); + result.setReferenceType(info.referenceType()); + result.setIndirectionsV(info.indirectionsV()); + result.decideUsagePattern(); + return result; +} + +bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info) +{ + AbstractMetaTypeList templateTypes; + + QString errorMessage; + for (const TypeInfo &i : info.instantiations()) { + const auto typeO = inheritTemplateParameter(subclass, templateClass, i, &errorMessage); + if (typeO.has_value()) { + templateTypes.append(typeO.value()); + } else { + errorMessage = msgInheritTemplateIssue(subclass, info, errorMessage); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + } + } + if (templateTypes.isEmpty()) { + errorMessage = msgInheritTemplateIssue(subclass, info, + "No template parameters could be inherited"_L1); + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return false; + } + return inheritTemplate(subclass, templateClass, templateTypes); +} + +bool AbstractMetaBuilderPrivate::inheritTemplate(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes) +{ + subclass->setTemplateBaseClass(templateClass); + subclass->setTemplateBaseClassInstantiations(templateTypes); + subclass->setBaseClass(templateClass->baseClass()); + return true; +} + +AbstractMetaFunctionPtr + AbstractMetaBuilderPrivate::inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes) +{ + AbstractMetaFunctionPtr f(function->copy()); + f->setArguments(AbstractMetaArgumentList()); + f->setFlags(f->flags() | AbstractMetaFunction::Flag::InheritedFromTemplate); + + if (!function->isVoid()) { + auto returnType = inheritTemplateType(templateTypes, function->type()); + if (!returnType.has_value()) + return {}; + f->setType(returnType.value()); + } + + const AbstractMetaArgumentList &arguments = function->arguments(); + for (const AbstractMetaArgument &argument : arguments) { + auto argType = inheritTemplateType(templateTypes, argument.type()); + if (!argType.has_value()) + return {}; + AbstractMetaArgument arg = argument; + arg.setType(argType.value()); + f->addArgument(arg); + } + + return f; +} + +AbstractMetaFunctionPtr + AbstractMetaBuilder::inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes) +{ + return AbstractMetaBuilderPrivate::inheritTemplateFunction(function, templateTypes); +} + +AbstractMetaFunctionPtr + AbstractMetaBuilderPrivate::inheritTemplateMember(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaClassPtr &subclass) +{ + AbstractMetaFunctionPtr f = inheritTemplateFunction(function, templateTypes); + if (!f) + return {}; + + // There is no base class in the target language to inherit from here, so + // the template instantiation is the class that implements the function. + f->setImplementingClass(subclass); + + // We also set it as the declaring class, since the superclass is + // supposed to disappear. This allows us to make certain function modifications + // on the inherited functions. + f->setDeclaringClass(subclass); + + if (f->isConstructor()) { + f->setName(subclass->name()); + f->setOriginalName(subclass->name()); + } + + ComplexTypeEntryPtr te = subclass->typeEntry(); + const FunctionModificationList mods = function->modifications(templateClass); + + for (auto mod : mods) { + mod.setSignature(f->minimalSignature()); + +// If we ever need it... Below is the code to do +// substitution of the template instantation type inside +// injected code.. +#if 0 + if (mod.modifiers & Modification::CodeInjection) { + for (int j = 0; j < template_types.size(); ++j) { + CodeSnip &snip = mod.snips.last(); + QString code = snip.code(); + code.replace(QString::fromLatin1("$$QT_TEMPLATE_%1$$").arg(j), + template_types.at(j)->typeEntry()->qualifiedCppName()); + snip.codeList.clear(); + snip.addCode(code); + } + } +#endif + te->addFunctionModification(mod); + } + + QString errorMessage; + if (!applyArrayArgumentModifications(f->modifications(subclass), f.get(), + &errorMessage)) { + qCWarning(lcShiboken, "While specializing %s (%s): %s", + qPrintable(subclass->name()), qPrintable(templateClass->name()), + qPrintable(errorMessage)); + } + return f; +} + +AbstractMetaFunctionPtr + AbstractMetaBuilder::inheritTemplateMember(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaClassPtr &subclass) +{ + return AbstractMetaBuilderPrivate::inheritTemplateMember(function, templateTypes, + templateClass, subclass); +} + +static bool doInheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaFunctionCList &existingSubclassFuncs, + const AbstractMetaClassCPtr &templateBaseClass, + const AbstractMetaClassCPtr &subclass) +{ + // If the function is modified or the instantiation has an equally named + // function we are shadowing, so we need to skip it (unless the subclass + // declares it via "using"). + if (function->isModifiedRemoved()) + return false; + if (function->isConstructor() && !subclass->isTypeDef()) + return false; + return AbstractMetaFunction::find(existingSubclassFuncs, function->name()) == nullptr + || subclass->isUsingMember(templateBaseClass, function->name(), Access::Protected); +} + +void AbstractMetaBuilderPrivate::inheritTemplateFunctions(const AbstractMetaClassPtr &subclass) +{ + auto templateClass = subclass->templateBaseClass(); + + if (subclass->isTypeDef()) { + subclass->setHashFunction(templateClass->hashFunction()); + subclass->setHasNonPrivateConstructor(templateClass->hasNonPrivateConstructor()); + subclass->setHasPrivateDestructor(templateClass->hasPrivateDestructor()); + subclass->setHasProtectedDestructor(templateClass->hasProtectedDestructor()); + subclass->setHasVirtualDestructor(templateClass->hasVirtualDestructor()); + } + + const auto &templateTypes = subclass->templateBaseClassInstantiations(); + const AbstractMetaFunctionCList existingSubclassFuncs = + subclass->functions(); // Take copy + const auto &templateClassFunctions = templateClass->functions(); + for (const auto &function : templateClassFunctions) { + if (doInheritTemplateFunction(function, existingSubclassFuncs, + templateClass, subclass)) { + AbstractMetaFunctionCPtr f = inheritTemplateMember(function, templateTypes, + templateClass, subclass); + if (f) + AbstractMetaClass::addFunction(subclass, f); + } + } + + // Take copy + const AbstractMetaFieldList existingSubclassFields = subclass->fields(); + const AbstractMetaFieldList &templateClassFields = templateClass->fields(); + for (const AbstractMetaField &field : templateClassFields) { + // If the field is modified or the instantiation has a field named + // the same as an existing field we have shadowing, so we need to skip it. + if (field.isModifiedRemoved() || field.isStatic() + || AbstractMetaField::find(existingSubclassFields, field.name()).has_value()) { + continue; + } + + AbstractMetaField f = field; + f.setEnclosingClass(subclass); + auto fieldType = inheritTemplateType(templateTypes, field.type()); + if (!fieldType.has_value()) + continue; + f.setType(fieldType.value()); + subclass->addField(f); + } +} + +void AbstractMetaBuilderPrivate::parseQ_Properties(const AbstractMetaClassPtr &metaClass, + const QStringList &declarations) +{ + const QStringList scopes = currentScope()->qualifiedName(); + QString errorMessage; + int i = 0; + for (; i < declarations.size(); ++i) { + auto spec = QPropertySpec::parseQ_Property(this, metaClass, declarations.at(i), scopes, &errorMessage); + if (spec.has_value()) { + spec->setIndex(i); + metaClass->addPropertySpec(spec.value()); + } else if (!errorMessage.isEmpty()) { + QString message; + QTextStream str(&message); + str << metaClass->sourceLocation() << errorMessage; + qCWarning(lcShiboken, "%s", qPrintable(message)); + } + } + + // User-added properties + auto typeEntry = metaClass->typeEntry(); + for (const TypeSystemProperty &tp : typeEntry->properties()) { + std::optional<QPropertySpec> spec; + if (metaClass->propertySpecByName(tp.name)) + errorMessage = msgPropertyExists(metaClass->name(), tp.name); + else + spec = QPropertySpec::fromTypeSystemProperty(this, metaClass, tp, scopes, &errorMessage); + + if (spec.has_value()) { + spec->setIndex(i++); + metaClass->addPropertySpec(spec.value()); + } else { + QString message; + QTextStream str(&message); + str << typeEntry->sourceLocation() << errorMessage; + qCWarning(lcShiboken, "%s", qPrintable(message)); + } + } +} + +void AbstractMetaBuilderPrivate::setupExternalConversion(const AbstractMetaClassCPtr &cls) +{ + const auto &convOps = cls->operatorOverloads(OperatorQueryOption::ConversionOp); + for (const auto &func : convOps) { + if (func->isModifiedRemoved()) + continue; + const auto metaClass = + AbstractMetaClass::findClass(m_metaClasses, func->type().typeEntry()); + if (!metaClass) + continue; + metaClass->addExternalConversionOperator(func); + } + for (const auto &innerClass : cls->innerClasses()) + setupExternalConversion(innerClass); +} + +static void writeRejectLogFile(const QString &name, + const AbstractMetaBuilderPrivate::RejectSet &rejects) +{ + static const QHash<AbstractMetaBuilder::RejectReason, QByteArray> descriptions ={ + {AbstractMetaBuilder::NotInTypeSystem, "Not in type system"_ba}, + {AbstractMetaBuilder::GenerationDisabled, "Generation disabled by type system"_ba}, + {AbstractMetaBuilder::RedefinedToNotClass, "Type redefined to not be a class"_ba}, + {AbstractMetaBuilder::UnmatchedReturnType, "Unmatched return type"_ba}, + {AbstractMetaBuilder::UnmatchedArgumentType, "Unmatched argument type"_ba}, + {AbstractMetaBuilder::UnmatchedOperator, "Unmatched operator"_ba}, + {AbstractMetaBuilder::Deprecated, "Deprecated"_ba} + }; + + QFile f(name); + if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) { + qCWarning(lcShiboken, "%s", qPrintable(msgCannotOpenForWriting(f))); + return; + } + + QTextStream s(&f); + + int lastReason = -1; + for (const auto &e : rejects) { + if (e.reason != lastReason) { + const QByteArray description = descriptions.value(e.reason, "Unknown reason"_ba); + const QByteArray underline(description.size(), '*'); + if (lastReason != -1) + s << '\n'; + s << underline << '\n' << description << '\n' << underline << "\n\n"; + lastReason = e.reason; + } + + s << " - " << e << '\n'; + } +} + +void AbstractMetaBuilderPrivate::dumpLog() const +{ + writeRejectLogFile(m_logDirectory + u"mjb_rejected_classes.log"_s, m_rejectedClasses); + writeRejectLogFile(m_logDirectory + u"mjb_rejected_enums.log"_s, m_rejectedEnums); + writeRejectLogFile(m_logDirectory + u"mjb_rejected_functions.log"_s, m_rejectedFunctions); + writeRejectLogFile(m_logDirectory + u"mjb_rejected_fields.log"_s, m_rejectedFields); +} + +// Topological sorting of classes. Templates for use with +// AbstractMetaClassList/AbstractMetaClassCList. +// Add a dependency of the class associated with typeEntry on clazz. +template <class MetaClass> +static bool addClassDependency(const QList<std::shared_ptr<MetaClass> > &classList, + const TypeEntryCPtr &typeEntry, + std::shared_ptr<MetaClass> clazz, + Graph<std::shared_ptr<MetaClass> > *graph) +{ + if (!typeEntry->isComplex() || typeEntry == clazz->typeEntry()) + return false; + const auto c = AbstractMetaClass::findClass(classList, typeEntry); + if (c == nullptr || c->enclosingClass() == clazz) + return false; + return graph->addEdge(c, clazz); +} + +template <class MetaClass> +static QList<std::shared_ptr<MetaClass> > + topologicalSortHelper(const QList<std::shared_ptr<MetaClass> > &classList, + const Dependencies &additionalDependencies) +{ + Graph<std::shared_ptr<MetaClass> > graph(classList.cbegin(), classList.cend()); + + for (const auto &dep : additionalDependencies) { + if (!graph.addEdge(dep.parent, dep.child)) { + qCWarning(lcShiboken).noquote().nospace() + << "AbstractMetaBuilder::classesTopologicalSorted(): Invalid additional dependency: " + << dep.child->name() << " -> " << dep.parent->name() << '.'; + } + } + + for (const auto &clazz : classList) { + if (auto enclosingC = clazz->enclosingClass()) { + const auto enclosing = std::const_pointer_cast<MetaClass>(enclosingC); + graph.addEdge(enclosing, clazz); + } + + for (const auto &baseClass : clazz->baseClasses()) + graph.addEdge(std::const_pointer_cast<MetaClass>(baseClass), clazz); + + for (const auto &func : clazz->functions()) { + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + // Check methods with default args: If a class is instantiated by value, + // ("QString s = QString()"), add a dependency. + if (!arg.originalDefaultValueExpression().isEmpty() + && arg.type().isValue()) { + addClassDependency(classList, arg.type().typeEntry(), + clazz, &graph); + } + } + } + // Member fields need to be initialized + for (const AbstractMetaField &field : clazz->fields()) { + auto typeEntry = field.type().typeEntry(); + if (typeEntry->isEnum()) // Enum defined in class? + typeEntry = typeEntry->parent(); + if (typeEntry != nullptr) + addClassDependency(classList, typeEntry, clazz, &graph); + } + } + + const auto result = graph.topologicalSort(); + if (!result.isValid() && graph.nodeCount()) { + QTemporaryFile tempFile(QDir::tempPath() + u"/cyclic_depXXXXXX.dot"_s); + tempFile.setAutoRemove(false); + const bool ok = tempFile.open(); + if (ok) { + graph.dumpDot(tempFile.fileName(), + [] (const AbstractMetaClassCPtr &c) { return c->name(); }); + } + + QString message; + QTextStream str(&message); + str << "Cyclic dependency of classes found:"; + for (const auto &c : result.cyclic) + str << ' ' << c->name(); + str << '.'; + if (ok) { + str << " Graph can be found at \"" + << QDir::toNativeSeparators(tempFile.fileName()) << '"'; + } + qCWarning(lcShiboken, "%s", qPrintable(message)); + } + + return result.result; +} + +AbstractMetaClassList + AbstractMetaBuilderPrivate::classesTopologicalSorted(const AbstractMetaClassList &classList, + const Dependencies &additionalDependencies) +{ + return topologicalSortHelper(classList, additionalDependencies); +} + +AbstractMetaClassCList + AbstractMetaBuilderPrivate::classesTopologicalSorted(const AbstractMetaClassCList &classList, + const Dependencies &additionalDependencies) +{ + return topologicalSortHelper(classList, additionalDependencies); +} + +void AbstractMetaBuilderPrivate::pushScope(const NamespaceModelItem &item) +{ + // For purposes of type lookup, join all namespaces of the same name + // within the parent item. + QList<NamespaceModelItem> candidates; + const QString name = item->name(); + if (!m_scopes.isEmpty()) { + for (const auto &n : m_scopes.constLast()->namespaces()) { + if (n->name() == name) + candidates.append(n); + } + } + if (candidates.size() > 1) { + auto joined = std::make_shared<_NamespaceModelItem>(m_scopes.constLast()->model(), + name, _CodeModelItem::Kind_Namespace); + joined->setScope(item->scope()); + for (const auto &n : candidates) + joined->appendNamespace(*n); + m_scopes << joined; + } else { + m_scopes << item; + } +} + +void AbstractMetaBuilder::setGlobalHeaders(const QFileInfoList &globalHeaders) +{ + d->m_globalHeaders = globalHeaders; +} + +void AbstractMetaBuilder::setHeaderPaths(const HeaderPaths &hp) +{ + for (const auto & h: hp) { + if (h.type != HeaderType::Framework && h.type != HeaderType::FrameworkSystem) + d->m_headerPaths.append(QFile::decodeName(h.path)); + } +} + +void AbstractMetaBuilder::setUseGlobalHeader(bool h) +{ + AbstractMetaBuilderPrivate::m_useGlobalHeader = h; +} + +void AbstractMetaBuilder::setSkipDeprecated(bool value) +{ + d->m_skipDeprecated = value; +} + +void AbstractMetaBuilder::setApiExtractorFlags(ApiExtractorFlags flags) +{ + d->m_apiExtractorFlags = flags; +} + +// PYSIDE-975: When receiving an absolute path name from the code model, try +// to resolve it against the include paths set on shiboken in order to recreate +// relative paths like #include <foo/bar.h>. + +static inline bool isFileSystemSlash(QChar c) +{ + return c == u'/' || c == u'\\'; +} + +static bool matchHeader(const QString &headerPath, const QString &fileName) +{ +#if defined(Q_OS_WIN) || defined(Q_OS_DARWIN) + static const Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; +#else + static const Qt::CaseSensitivity caseSensitivity = Qt::CaseSensitive; +#endif + const auto pathSize = headerPath.size(); + return fileName.size() > pathSize + && isFileSystemSlash(fileName.at(pathSize)) + && fileName.startsWith(headerPath, caseSensitivity); +} + +void AbstractMetaBuilderPrivate::setInclude(const TypeEntryPtr &te, const QString &path) const +{ + auto it = m_resolveIncludeHash.find(path); + if (it == m_resolveIncludeHash.end()) { + QFileInfo info(path); + const QString fileName = info.fileName(); + if (!m_useGlobalHeader + && std::any_of(m_globalHeaders.cbegin(), m_globalHeaders.cend(), + [fileName] (const QFileInfo &fi) { + return fi.fileName() == fileName; })) { + return; + } + + int bestMatchLength = 0; + for (const auto &headerPath : m_headerPaths) { + if (headerPath.size() > bestMatchLength && matchHeader(headerPath, path)) + bestMatchLength = headerPath.size(); + } + const QString include = bestMatchLength > 0 + ? path.right(path.size() - bestMatchLength - 1) : fileName; + it = m_resolveIncludeHash.insert(path, {Include::IncludePath, include}); + } + te->setInclude(it.value()); +} + +#ifndef QT_NO_DEBUG_STREAM +template <class Container> +static void debugFormatSequence(QDebug &d, const char *key, const Container& c, + const char *separator = ", ") +{ + if (c.isEmpty()) + return; + const auto begin = c.begin(); + const auto end = c.end(); + d << "\n " << key << '[' << c.size() << "]=("; + for (auto it = begin; it != end; ++it) { + if (it != begin) + d << separator; + d << *it; + } + d << ')'; +} + +void AbstractMetaBuilder::formatDebug(QDebug &debug) const +{ + debug << "m_globalHeader=" << d->m_globalHeaders; + debugFormatSequence(debug, "globalEnums", d->m_globalEnums, "\n"); + debugFormatSequence(debug, "globalFunctions", d->m_globalFunctions, "\n"); + if (const auto scopeCount = d->m_scopes.size()) { + debug << "\n scopes[" << scopeCount << "]=("; + for (qsizetype i = 0; i < scopeCount; ++i) { + if (i) + debug << ", "; + _CodeModelItem::formatKind(debug, d->m_scopes.at(i)->kind()); + debug << " \"" << d->m_scopes.at(i)->name() << '"'; + } + debug << ')'; + } + debugFormatSequence(debug, "classes", d->m_metaClasses, "\n"); + debugFormatSequence(debug, "templates", d->m_templates, "\n"); +} + +QDebug operator<<(QDebug d, const AbstractMetaBuilder &ab) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaBuilder("; + ab.formatDebug(d); + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder.h new file mode 100644 index 000000000..20261ed3c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder.h @@ -0,0 +1,152 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETABUILDER_H +#define ABSTRACTMETABUILDER_H + +#include "abstractmetalang_typedefs.h" +#include "apiextractorflags.h" +#include "header_paths.h" +#include "typesystem_enums.h" +#include "typesystem_typedefs.h" + +#include "clangparser/compilersupport.h" + +#include <QtCore/QFileInfoList> + +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QIODevice) + +class AbstractMetaBuilderPrivate; +class AbstractMetaClass; +class AbstractMetaType; +class AbstractMetaEnumValue; +class ComplexTypeEntry; +class TypeInfo; +class TypeEntry; + +class AbstractMetaBuilder +{ +public: + Q_DISABLE_COPY_MOVE(AbstractMetaBuilder) + + enum RejectReason { + NotInTypeSystem, + GenerationDisabled, + RedefinedToNotClass, + UnmatchedArgumentType, + UnmatchedReturnType, + UnmatchedOperator, + Deprecated, + NoReason + }; + + AbstractMetaBuilder(); + virtual ~AbstractMetaBuilder(); + + const AbstractMetaClassList &classes() const; + AbstractMetaClassList takeClasses(); + const AbstractMetaClassList &templates() const; + AbstractMetaClassList takeTemplates(); + const AbstractMetaClassList &smartPointers() const; + AbstractMetaClassList takeSmartPointers(); + const AbstractMetaFunctionCList &globalFunctions() const; + const AbstractMetaEnumList &globalEnums() const; + const QHash<TypeEntryCPtr, AbstractMetaEnum> &typeEntryToEnumsHash() const; + const QMultiHash<QString, QString> &typedefTargetToName() const; + + bool build(const QByteArrayList &arguments, + ApiExtractorFlags apiExtractorFlags = {}, + bool addCompilerSupportArguments = true, + LanguageLevel level = LanguageLevel::Default, + unsigned clangFlags = 0); + void setLogDirectory(const QString& logDir); + + /** + * AbstractMetaBuilder should know what's the global header being used, + * so any class declared under this header wont have the include file + * filled. + */ + void setGlobalHeaders(const QFileInfoList& globalHeaders); + void setHeaderPaths(const HeaderPaths &h); + + static void setUseGlobalHeader(bool h); + + void setSkipDeprecated(bool value); + + void setApiExtractorFlags(ApiExtractorFlags flags); + + enum TranslateTypeFlag { + DontResolveType = 0x1, + TemplateArgument = 0x2, + NoClassScopeLookup = 0x4 + }; + Q_DECLARE_FLAGS(TranslateTypeFlags, TranslateTypeFlag); + + static std::optional<AbstractMetaType> + translateType(const TypeInfo &_typei, const AbstractMetaClassPtr ¤tClass = {}, + TranslateTypeFlags flags = {}, QString *errorMessage = nullptr); + static std::optional<AbstractMetaType> + translateType(const QString &t, const AbstractMetaClassPtr ¤tClass = {}, + TranslateTypeFlags flags = {}, QString *errorMessage = nullptr); + + /// Performs a template specialization of the function. + /// \param function Function + /// \param templateTypes Instantiation types + /// \return Specialized copy of the function + static AbstractMetaFunctionPtr + inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes); + + static AbstractMetaClassPtr + inheritTemplateClass(const ComplexTypeEntryPtr &te, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes); + + /// Performs a template specialization of the member function. + /// \param function Member function + /// \param templateTypes Instantiation types + /// \param templateClass Template class + /// \param subclass Specialized class + /// \return Specialized copy of the function + static AbstractMetaFunctionPtr + inheritTemplateMember(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaClassPtr &subclass); + + static QString getSnakeCaseName(const QString &name); + // Names under which an item will be registered to Python depending on snakeCase + static QStringList definitionNames(const QString &name, + TypeSystem::SnakeCase snakeCase); + + static QString resolveScopePrefix(const AbstractMetaClassCPtr &scope, + QStringView value); + + static bool dontFixDefaultValue(QStringView expr); + + // For testing purposes + QString fixDefaultValue(const QString &expr, const AbstractMetaType &type, + const AbstractMetaClassCPtr &) const; + QString fixEnumDefault(const AbstractMetaType &type, const QString &expr, + const AbstractMetaClassCPtr & = {}) const; + + static void setCodeModelTestMode(bool b); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + friend class AbstractMetaBuilderPrivate; + AbstractMetaBuilderPrivate *d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractMetaBuilder::TranslateTypeFlags); + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const AbstractMetaBuilder &ab); +#endif + +#endif // ABSTRACTMETBUILDER_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp b/sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp new file mode 100644 index 000000000..68eef737a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_helpers.cpp @@ -0,0 +1,202 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetabuilder.h" +#include "abstractmetabuilder_p.h" +#include "abstractmetaenum.h" +#include "abstractmetafield.h" +#include "abstractmetalang.h" +#include "enumtypeentry.h" +#include "flagstypeentry.h" + +using namespace Qt::StringLiterals; + +using QStringViewList = QList<QStringView>; + +// Return a prefix to fully qualify value, eg: +// resolveScopePrefix("Class::NestedClass::Enum::Value1", "Enum::Value1") +// -> "Class::NestedClass::") +static QString resolveScopePrefixHelper(const QStringViewList &scopeList, + QStringView value) +{ + QString name; + for (qsizetype i = scopeList.size() - 1 ; i >= 0; --i) { + const QString prefix = scopeList.at(i).toString() + u"::"_s; + if (value.startsWith(prefix)) + name.clear(); + else + name.prepend(prefix); + } + return name; +} + +QString AbstractMetaBuilder::resolveScopePrefix(const AbstractMetaClassCPtr &scope, + QStringView value) +{ + if (!scope) + return {}; + const QString &qualifiedCppName = scope->qualifiedCppName(); + const QStringViewList scopeList = + QStringView{qualifiedCppName}.split(u"::"_s, Qt::SkipEmptyParts); + return resolveScopePrefixHelper(scopeList, value); +} + +// Return the scope for fully qualifying the enumeration value +static QString resolveEnumValueScopePrefix(const AbstractMetaEnum &metaEnum, + QStringView value) +{ + AbstractMetaClassCPtr scope = metaEnum.enclosingClass(); + if (!scope) + return {}; // global enum, value should work as is + const QString &qualifiedCppName = scope->qualifiedCppName(); + const QString &enumName = metaEnum.name(); + QStringViewList parts = + QStringView{qualifiedCppName}.split(u"::"_s, Qt::SkipEmptyParts); + // Append the type (as required for enum classes) unless it is an anonymous enum. + if (!metaEnum.isAnonymous()) + parts.append(QStringView{enumName}); + return resolveScopePrefixHelper(parts, value); +} + +bool AbstractMetaBuilderPrivate::isQualifiedCppIdentifier(QStringView e) +{ + return !e.isEmpty() && e.at(0).isLetter() + && std::all_of(e.cbegin() + 1, e.cend(), + [](QChar c) { return c.isLetterOrNumber() || c == u'_' || c == u':'; }); +} + +static bool isIntegerConstant(const QStringView expr) +{ + bool isNumber; + auto n = expr.toInt(&isNumber, /* guess base: 0x or decimal */ 0); + Q_UNUSED(n); + return isNumber; +} + +static bool isFloatConstant(const QStringView expr) +{ + bool isNumber; + auto d = expr.toDouble(&isNumber); + Q_UNUSED(d); + return isNumber; +} + +// Fix an enum default value: Add the enum/flag scope or fully qualified name +// to the default value, making it usable from Python wrapper code outside the +// owner class hierarchy. See TestEnum::testEnumDefaultValues(). +QString AbstractMetaBuilderPrivate::fixEnumDefault(const AbstractMetaType &type, + const QString &expr, + const AbstractMetaClassCPtr &klass) const +{ + // QFlags construct from integers, do not fix that + if (isIntegerConstant(expr)) + return expr; + + const QString field = qualifyStaticField(klass, expr); + if (!field.isEmpty()) + return field; + + const auto typeEntry = type.typeEntry(); + EnumTypeEntryCPtr enumTypeEntry; + FlagsTypeEntryCPtr flagsTypeEntry; + if (typeEntry->isFlags()) { + flagsTypeEntry = std::static_pointer_cast<const FlagsTypeEntry>(typeEntry); + enumTypeEntry = flagsTypeEntry->originator(); + } else { + Q_ASSERT(typeEntry->isEnum()); + enumTypeEntry = std::static_pointer_cast<const EnumTypeEntry>(typeEntry); + } + // Use the enum's qualified name (would otherwise be "QFlags<Enum>") + if (!enumTypeEntry->qualifiedCppName().contains(u"::")) + return expr; // Global enum, nothing to fix here + + // This is a somehow scoped enum + AbstractMetaEnum metaEnum = m_enums.value(enumTypeEntry); + + if (isQualifiedCppIdentifier(expr)) // A single enum value + return resolveEnumValueScopePrefix(metaEnum, expr) + expr; + + QString result; + // Is this a cast from integer or other type ("Enum(-1)" or "Options(0x10|0x20)"? + // Prepend the scope (assuming enum and flags are in the same scope). + auto parenPos = expr.indexOf(u'('); + const bool typeCast = parenPos != -1 && expr.endsWith(u')') + && isQualifiedCppIdentifier(QStringView{expr}.left(parenPos)); + if (typeCast) { + const QString prefix = + AbstractMetaBuilder::resolveScopePrefix(metaEnum.enclosingClass(), expr); + result += prefix; + parenPos += prefix.size(); + } + result += expr; + + // Extract "Option1 | Option2" from "Options(Option1 | Option2)" + QStringView innerExpression = typeCast + ? QStringView{result}.mid(parenPos + 1, result.size() - parenPos - 2) + : QStringView{result}; + + // Quick check for number "Options(0x4)" + if (isIntegerConstant(innerExpression)) + return result; + + // Quick check for single enum value "Options(Option1)" + if (isQualifiedCppIdentifier(innerExpression)) { + const QString prefix = resolveEnumValueScopePrefix(metaEnum, innerExpression); + result.insert(parenPos + 1, prefix); + return result; + } + + // Tokenize simple "A | B" expressions and qualify the enum values therein. + // Anything more complicated is left as is ATM. + if (!innerExpression.contains(u'|') || innerExpression.contains(u'&') + || innerExpression.contains(u'^') || innerExpression.contains(u'(') + || innerExpression.contains(u'~')) { + return result; + } + + const QList<QStringView> tokens = innerExpression.split(u'|', Qt::SkipEmptyParts); + QStringList qualifiedTokens; + qualifiedTokens.reserve(tokens.size()); + for (const auto &tokenIn : tokens) { + const auto token = tokenIn.trimmed(); + QString qualified = token.toString(); + if (!isIntegerConstant(token) && isQualifiedCppIdentifier(token)) + qualified.prepend(resolveEnumValueScopePrefix(metaEnum, token)); + qualifiedTokens.append(qualified); + } + const QString qualifiedExpression = qualifiedTokens.join(u" | "_s); + if (!typeCast) + return qualifiedExpression; + + result.replace(parenPos + 1, innerExpression.size(), qualifiedExpression); + return result; +} + +bool AbstractMetaBuilder::dontFixDefaultValue(QStringView expr) +{ + return expr.isEmpty() || expr == u"{}" || expr == u"nullptr" + || expr == u"NULL" || expr == u"true" || expr == u"false" + || (expr.startsWith(u'{') && expr.startsWith(u'}')) // initializer list + || (expr.startsWith(u'[') && expr.startsWith(u']')) // array + || expr.startsWith(u"Qt::") // Qt namespace constant + || isIntegerConstant(expr) || isFloatConstant(expr); +} + +QString AbstractMetaBuilderPrivate::qualifyStaticField(const AbstractMetaClassCPtr &c, + QStringView field) +{ + if (!c || c->fields().isEmpty()) + return {}; + // If there is a scope, ensure it matches the class + const auto lastQualifier = field.lastIndexOf(u"::"); + if (lastQualifier != -1 + && !c->qualifiedCppName().endsWith(field.left(lastQualifier))) { + return {}; + } + const auto fieldName = lastQualifier != -1 + ? field.mid(lastQualifier + 2) : field; + const auto fieldOpt = c->findField(fieldName); + if (!fieldOpt.has_value() || !fieldOpt.value().isStatic()) + return {}; + return AbstractMetaBuilder::resolveScopePrefix(c, field) + field.toString(); +} diff --git a/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h new file mode 100644 index 000000000..e65a4f176 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetabuilder_p.h @@ -0,0 +1,254 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETABUILDER_P_H +#define ABSTRACTMETABUILDER_P_H + +#include "abstractmetabuilder.h" +#include "dependency.h" +#include "parser/codemodel_fwd.h" +#include "abstractmetalang.h" +#include "abstractmetatype.h" +#include "include.h" +#include "typeparser.h" +#include "modifications_typedefs.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QFileInfo> +#include <QtCore/QList> +#include <QtCore/QMap> +#include <QtCore/QMultiHash> +#include <QtCore/QSet> + +#include <optional> +#include <set> + +class TypeDatabase; + +struct RejectEntry +{ + AbstractMetaBuilder::RejectReason reason; + QString signature; + QString sortkey; + QString message; +}; + +bool operator<(const RejectEntry &re1, const RejectEntry &re2); + +class AbstractMetaBuilderPrivate +{ +public: + struct TypeClassEntry + { + AbstractMetaType type; + AbstractMetaClassCPtr klass; + }; + + using TranslateTypeFlags = AbstractMetaBuilder::TranslateTypeFlags; + + Q_DISABLE_COPY(AbstractMetaBuilderPrivate) + + AbstractMetaBuilderPrivate(); + + static FileModelItem buildDom(QByteArrayList arguments, + bool addCompilerSupportArguments, + LanguageLevel level, + unsigned clangFlags); + void traverseDom(const FileModelItem &dom, ApiExtractorFlags flags); + + void dumpLog() const; + + static AbstractMetaClassList + classesTopologicalSorted(const AbstractMetaClassList &classList, + const Dependencies &additionalDependencies = {}); + static AbstractMetaClassCList + classesTopologicalSorted(const AbstractMetaClassCList &classList, + const Dependencies &additionalDependencies = {}); + + NamespaceModelItem popScope() { return m_scopes.takeLast(); } + + void pushScope(const NamespaceModelItem &item); + + NamespaceModelItem currentScope() const { return m_scopes.constLast(); } + + AbstractMetaClassPtr argumentToClass(const ArgumentModelItem &, + const AbstractMetaClassCPtr ¤tClass); + + void addAbstractMetaClass(const AbstractMetaClassPtr &cls, const _CodeModelItem *item); + AbstractMetaClassPtr traverseTypeDef(const FileModelItem &dom, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass); + AbstractMetaClassPtr traverseTypeDefHelper(const FileModelItem &dom, + const TypeDefModelItem &typeDef, + const AbstractMetaClassPtr ¤tClass); + void traverseTypesystemTypedefs(); + AbstractMetaClassPtr traverseClass(const FileModelItem &dom, + const ClassModelItem &item, + const AbstractMetaClassPtr ¤tClass); + void traverseScopeMembers(const ScopeModelItem &item, + const AbstractMetaClassPtr &metaClass); + void traverseClassMembers(const ClassModelItem &scopeItem); + void traverseUsingMembers(const AbstractMetaClassPtr &metaClass) const; + void traverseNamespaceMembers(const NamespaceModelItem &scopeItem); + bool setupInheritance(const AbstractMetaClassPtr &metaClass); + AbstractMetaClassPtr traverseNamespace(const FileModelItem &dom, + const NamespaceModelItem &item); + std::optional<AbstractMetaEnum> traverseEnum(const EnumModelItem &item, + const AbstractMetaClassPtr &enclosing, + const QSet<QString> &enumsDeclarations); + void traverseEnums(const ScopeModelItem &item, const AbstractMetaClassPtr &parent, + const QStringList &enumsDeclarations); + AbstractMetaFunctionRawPtrList classFunctionList(const ScopeModelItem &scopeItem, + AbstractMetaClass::Attributes *constructorAttributes, + const AbstractMetaClassPtr ¤tClass); + void traverseFunctions(const ScopeModelItem& item, + const AbstractMetaClassPtr &parent); + static void applyFunctionModifications(AbstractMetaFunction *func); + void traverseFields(const ScopeModelItem &item, const AbstractMetaClassPtr &parent); + bool traverseStreamOperator(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass); + void traverseOperatorFunction(const FunctionModelItem &item, + const AbstractMetaClassPtr ¤tClass); + AbstractMetaFunction *traverseAddedFunctionHelper(const AddedFunctionPtr &addedFunc, + const AbstractMetaClassPtr &metaClass, + QString *errorMessage); + bool traverseAddedGlobalFunction(const AddedFunctionPtr &addedFunc, + QString *errorMessage); + bool traverseAddedMemberFunction(const AddedFunctionPtr &addedFunc, + const AbstractMetaClassPtr &metaClass, + QString *errorMessage); + void rejectFunction(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass, + AbstractMetaBuilder::RejectReason reason, + const QString &rejectReason); + AbstractMetaFunction *traverseFunction(const FunctionModelItem &function, + const AbstractMetaClassPtr ¤tClass); + std::optional<AbstractMetaField> traverseField(const VariableModelItem &field, + const AbstractMetaClassCPtr &cls); + void checkFunctionModifications() const; + void registerHashFunction(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass); + void registerToStringCapabilityIn(const NamespaceModelItem &namespaceItem); + void registerToStringCapability(const FunctionModelItem &functionItem, + const AbstractMetaClassPtr ¤tClass); + + /** + * A conversion operator function should not have its owner class as + * its return type, but unfortunately it does. This function fixes the + * return type of operator functions of this kind making the return type + * be the same as it is supposed to generate when used in C++. + * If the returned type is a wrapped C++ class, this method also adds the + * conversion operator to the collection of external conversions of the + * said class. + * \param metaFunction conversion operator function to be fixed. + */ + static void fixReturnTypeOfConversionOperator(AbstractMetaFunction *metaFunction); + + void parseQ_Properties(const AbstractMetaClassPtr &metaClass, + const QStringList &declarations); + void setupEquals(const AbstractMetaClassPtr &metaClass); + void setupComparable(const AbstractMetaClassPtr &metaClass); + void setupExternalConversion(const AbstractMetaClassCPtr &cls); + + static bool isQualifiedCppIdentifier(QStringView e); + QString fixDefaultValue(QString expr, const AbstractMetaType &type, + const AbstractMetaClassCPtr &) const; + QString fixSimpleDefaultValue(QStringView expr, + const AbstractMetaClassCPtr &klass) const; + + QString fixEnumDefault(const AbstractMetaType &type, const QString &expr, + const AbstractMetaClassCPtr &) const; + /// Qualify a static field name for default value expressions + static QString qualifyStaticField(const AbstractMetaClassCPtr &c, QStringView field); + + std::optional<AbstractMetaType> + translateType(const TypeInfo &type, const AbstractMetaClassCPtr ¤tClass, + TranslateTypeFlags flags = {}, QString *errorMessage = nullptr); + static std::optional<AbstractMetaType> + translateTypeStatic(const TypeInfo &type, const AbstractMetaClassCPtr ¤t, + AbstractMetaBuilderPrivate *d = nullptr, TranslateTypeFlags flags = {}, + QString *errorMessageIn = nullptr); + static TypeEntryCList findTypeEntriesHelper(const QString &qualifiedName, const QString &name, + TranslateTypeFlags flags = {}, + const AbstractMetaClassCPtr ¤tClass = {}, + AbstractMetaBuilderPrivate *d = nullptr); + static TypeEntryCList findTypeEntries(const QString &qualifiedName, const QString &name, + TranslateTypeFlags flags = {}, + const AbstractMetaClassCPtr ¤tClass = {}, + AbstractMetaBuilderPrivate *d = nullptr, + QString *errorMessage = nullptr); + + qint64 findOutValueFromString(const QString &stringValue, bool &ok); + + AbstractMetaClassPtr findTemplateClass(const QString& name, const AbstractMetaClassCPtr &context, + TypeInfo *info = nullptr, + ComplexTypeEntryPtr *baseContainerType = nullptr) const; + AbstractMetaClassCList getBaseClasses(const AbstractMetaClassCPtr &metaClass) const; + + static bool inheritTemplate(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const TypeInfo &info); + static bool inheritTemplate(const AbstractMetaClassPtr &subclass, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes); + + static AbstractMetaFunctionPtr + inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes); + + static AbstractMetaFunctionPtr + inheritTemplateMember(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaClassPtr &subclass); + + static void inheritTemplateFunctions(const AbstractMetaClassPtr &subclass); + static std::optional<AbstractMetaType> + inheritTemplateType(const AbstractMetaTypeList &templateTypes, + const AbstractMetaType &metaType); + + static bool isQObject(const FileModelItem &dom, const QString &qualifiedName); + static bool isEnum(const FileModelItem &dom, const QStringList &qualifiedName); + + void sortLists(); + void setInclude(const TypeEntryPtr &te, const QString &path) const; + static void fixArgumentNames(AbstractMetaFunction *func, const FunctionModificationList &mods); + + void fillAddedFunctions(const AbstractMetaClassPtr &metaClass); + AbstractMetaClassCPtr resolveTypeSystemTypeDef(const AbstractMetaType &t) const; + + void fixSmartPointers(); + + AbstractMetaBuilder *q = nullptr; + AbstractMetaClassList m_metaClasses; + AbstractMetaClassList m_templates; + AbstractMetaClassList m_smartPointers; + QHash<const _CodeModelItem *, AbstractMetaClassPtr > m_itemToClass; + QHash<AbstractMetaClassCPtr, const _CodeModelItem *> m_classToItem; + AbstractMetaFunctionCList m_globalFunctions; + AbstractMetaEnumList m_globalEnums; + + using RejectSet = std::set<RejectEntry>; + + RejectSet m_rejectedClasses; + RejectSet m_rejectedEnums; + RejectSet m_rejectedFunctions; + RejectSet m_rejectedFields; + + QHash<TypeEntryCPtr, AbstractMetaEnum> m_enums; + + QList<NamespaceModelItem> m_scopes; + + QString m_logDirectory; + QFileInfoList m_globalHeaders; + QStringList m_headerPaths; + mutable QHash<QString, Include> m_resolveIncludeHash; + QMultiHash<QString, QString> m_typedefTargetToName; + QList<TypeClassEntry> m_typeSystemTypeDefs; // look up metatype->class for type system typedefs + ApiExtractorFlags m_apiExtractorFlags; + bool m_skipDeprecated = false; + static bool m_useGlobalHeader; + static bool m_codeModelTestMode; +}; + +#endif // ABSTRACTMETBUILDER_P_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetaenum.cpp b/sources/shiboken6/ApiExtractor/abstractmetaenum.cpp new file mode 100644 index 000000000..780170c22 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetaenum.cpp @@ -0,0 +1,423 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetaenum.h" +#include "abstractmetalang.h" +#include "documentation.h" +#include "enumtypeentry.h" +#include "parser/enumvalue.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> + +#include <algorithm> + +using namespace Qt::StringLiterals; + +class AbstractMetaEnumValueData : public QSharedData +{ +public: + QString m_name; + QString m_stringValue; + EnumValue m_value; + Documentation m_doc; + bool m_deprecated = false; +}; + +AbstractMetaEnumValue::AbstractMetaEnumValue() : + d(new AbstractMetaEnumValueData) +{ +} + +AbstractMetaEnumValue::AbstractMetaEnumValue(const AbstractMetaEnumValue &) = default; +AbstractMetaEnumValue &AbstractMetaEnumValue::operator=(const AbstractMetaEnumValue &) = default; +AbstractMetaEnumValue::AbstractMetaEnumValue(AbstractMetaEnumValue &&) noexcept = default; +AbstractMetaEnumValue &AbstractMetaEnumValue::operator=(AbstractMetaEnumValue &&) noexcept = default; +AbstractMetaEnumValue::~AbstractMetaEnumValue() = default; + +EnumValue AbstractMetaEnumValue::value() const +{ + return d->m_value; +} + +void AbstractMetaEnumValue::setValue(EnumValue value) +{ + if (d->m_value != value) + d->m_value = value; +} + +QString AbstractMetaEnumValue::stringValue() const +{ + return d->m_stringValue; +} + +void AbstractMetaEnumValue::setStringValue(const QString &v) +{ + if (d->m_stringValue != v) + d->m_stringValue = v; +} + +QString AbstractMetaEnumValue::name() const +{ + return d->m_name; +} + +void AbstractMetaEnumValue::setName(const QString &name) +{ + if (d->m_name != name) + d->m_name = name; +} + +bool AbstractMetaEnumValue::isDeprecated() const +{ + return d->m_deprecated; +} + +void AbstractMetaEnumValue::setDeprecated(bool deprecated) +{ + if (d->m_deprecated != deprecated) + d->m_deprecated = deprecated; +} + +Documentation AbstractMetaEnumValue::documentation() const +{ + return d->m_doc; +} + +void AbstractMetaEnumValue::setDocumentation(const Documentation &doc) +{ + if (d->m_doc != doc) + d->m_doc = doc; +} + +// --------------------- AbstractMetaEnum + +class AbstractMetaEnumData : public QSharedData +{ +public: + AbstractMetaEnumData() : m_deprecated(false), + m_hasQenumsDeclaration(false), m_signed(true) + { + } + + int unsignedUsedBits() const; + int signedUsedBits() const; + + AbstractMetaEnumValueList m_enumValues; + + EnumTypeEntryCPtr m_typeEntry; + Documentation m_doc; + QString m_underlyingType; + + EnumKind m_enumKind = CEnum; + Access m_access = Access::Public; + uint m_deprecated : 1; + uint m_hasQenumsDeclaration : 1; + uint m_signed : 1; +}; + +static int _usedBits(uint64_t v) +{ + return (v >> 32) ? 64 : (v >> 16) ? 32 : (v >> 8) ? 16 : 8; +} + +static int _usedBits(int64_t v) +{ + return (v >> 31) ? 64 : (v >> 15) ? 32 : (v >> 7) ? 16 : 8; +} + +int AbstractMetaEnumData::unsignedUsedBits() const +{ + uint64_t maxValue = 0; + for (const auto &v : m_enumValues) { + if (const auto uv = v.value().unsignedValue(); uv > maxValue) + maxValue = uv; + } + return _usedBits(maxValue); +} + +int AbstractMetaEnumData::signedUsedBits() const +{ + int64_t maxValue = 0; + for (const auto &v : m_enumValues) { + const auto sv = v.value().value(); + const auto absV = sv < 0 ? ~sv : sv; + if (absV > maxValue) + maxValue = absV; + } + return _usedBits(maxValue); +} + +AbstractMetaEnum::AbstractMetaEnum() : d(new AbstractMetaEnumData) +{ +} + +AbstractMetaEnum::AbstractMetaEnum(const AbstractMetaEnum &) = default; +AbstractMetaEnum &AbstractMetaEnum::operator=(const AbstractMetaEnum&) = default; +AbstractMetaEnum::AbstractMetaEnum(AbstractMetaEnum &&) noexcept = default; +AbstractMetaEnum &AbstractMetaEnum::operator=(AbstractMetaEnum &&) noexcept = default; +AbstractMetaEnum::~AbstractMetaEnum() = default; + +const AbstractMetaEnumValueList &AbstractMetaEnum::values() const +{ + return d->m_enumValues; +} + +AbstractMetaEnumValueList AbstractMetaEnum::nonRejectedValues() const +{ + auto te = d->m_typeEntry; + AbstractMetaEnumValueList result = d->m_enumValues; + auto pred = [te](const AbstractMetaEnumValue &v) { + return te->isEnumValueRejected(v.name()); }; + result.erase(std::remove_if(result.begin(), result.end(), pred), result.end()); + return result; +} + +void AbstractMetaEnum::addEnumValue(const AbstractMetaEnumValue &enumValue) +{ + d->m_enumValues << enumValue; +} + +std::optional<AbstractMetaEnumValue> + findMatchingEnumValue(const AbstractMetaEnumValueList &list, QStringView value) +{ + for (const AbstractMetaEnumValue &enumValue : list) { + if (enumValue.name() == value) + return enumValue; + } + return {}; +} + +// Find enum values for "enum Enum { e1 }" either for "e1" or "Enum::e1" +std::optional<AbstractMetaEnumValue> + AbstractMetaEnum::findEnumValue(QStringView value) const +{ + if (isAnonymous()) + return findMatchingEnumValue(d->m_enumValues, value); + const int sepPos = value.indexOf(u"::"); + if (sepPos == -1) + return findMatchingEnumValue(d->m_enumValues, value); + if (name() == value.left(sepPos)) + return findMatchingEnumValue(d->m_enumValues, value.right(value.size() - sepPos - 2)); + return {}; +} + +QString AbstractMetaEnum::name() const +{ + return d->m_typeEntry->targetLangEntryName(); +} + +QString AbstractMetaEnum::qualifiedCppName() const +{ + return enclosingClass() + ? enclosingClass()->qualifiedCppName() + u"::"_s + name() + : name(); +} + +Access AbstractMetaEnum::access() const +{ + return d->m_access; +} + +void AbstractMetaEnum::setAccess(Access a) +{ + if (a != d->m_access) + d->m_access = a; +} + +bool AbstractMetaEnum::isDeprecated() const +{ + return d->m_deprecated; +} + +void AbstractMetaEnum::setDeprecated(bool deprecated) +{ + if (d->m_deprecated != deprecated) + d->m_deprecated = deprecated; +} + +static bool isDeprecatedValue(const AbstractMetaEnumValue &v) +{ + return v.isDeprecated(); +}; + +bool AbstractMetaEnum::hasDeprecatedValues() const +{ + return std::any_of(d->m_enumValues.cbegin(), d->m_enumValues.cend(), + isDeprecatedValue); +} + +AbstractMetaEnumValueList AbstractMetaEnum::deprecatedValues() const +{ + AbstractMetaEnumValueList result; + std::copy_if(d->m_enumValues.cbegin(), d->m_enumValues.cend(), + std::back_inserter(result), isDeprecatedValue); + return result; +} + +const Documentation &AbstractMetaEnum::documentation() const +{ + return d->m_doc; +} + +void AbstractMetaEnum::setDocumentation(const Documentation &doc) +{ + if (d->m_doc != doc) + d->m_doc = doc; +} + +QString AbstractMetaEnum::qualifier() const +{ + return d->m_typeEntry->targetLangQualifier(); +} + +QString AbstractMetaEnum::package() const +{ + return d->m_typeEntry->targetLangPackage(); +} + +QString AbstractMetaEnum::fullName() const +{ + return package() + u'.' + qualifier() + u'.' + name(); +} + +EnumKind AbstractMetaEnum::enumKind() const +{ + return d->m_enumKind; +} + +void AbstractMetaEnum::setEnumKind(EnumKind kind) +{ + if (d->m_enumKind != kind) + d->m_enumKind = kind; +} + +bool AbstractMetaEnum::isAnonymous() const +{ + return d->m_enumKind == AnonymousEnum; +} + +bool AbstractMetaEnum::hasQEnumsDeclaration() const +{ + return d->m_hasQenumsDeclaration; +} + +void AbstractMetaEnum::setHasQEnumsDeclaration(bool on) +{ + if (d->m_hasQenumsDeclaration != on) + d->m_hasQenumsDeclaration = on; +} + +EnumTypeEntryCPtr AbstractMetaEnum::typeEntry() const +{ + return d->m_typeEntry; +} + +void AbstractMetaEnum::setTypeEntry(const EnumTypeEntryCPtr &entry) +{ + if (d->m_typeEntry != entry) + d->m_typeEntry = entry; +} + +bool AbstractMetaEnum::isSigned() const +{ + return d->m_signed; +} + +void AbstractMetaEnum::setSigned(bool s) +{ + if (d->m_signed != s) + d->m_signed = s; +} + +QString AbstractMetaEnum::underlyingType() const +{ + return d->m_underlyingType; +} + +void AbstractMetaEnum::setUnderlyingType(const QString &underlyingType) +{ + if (d->m_underlyingType != underlyingType) + d->m_underlyingType = underlyingType; +} + +int AbstractMetaEnum::usedBits() const +{ + return isSigned() ? d->signedUsedBits() : d->unsignedUsedBits(); +} + +QString AbstractMetaEnum::intTypeForSize(int usedBits, bool isSigned) +{ + QString result = u"int"_s + QString::number(usedBits) + u"_t"_s; + return isSigned ? result : u'u' + result; +} + +#ifndef QT_NO_DEBUG_STREAM + +static void formatMetaEnumValue(QDebug &d, const AbstractMetaEnumValue &v, bool forceHex = false) +{ + d << v.name() << '='; + if (forceHex) + v.value().formatDebugHex(d); + else + v.value().formatDebug(d); + if (v.isDeprecated()) + d << " (deprecated)"; +} + +QDebug operator<<(QDebug d, const AbstractMetaEnumValue &v) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaEnumValue("; + formatMetaEnumValue(d, v); + d << ')'; + return d; +} + +static void formatMetaEnum(QDebug &d, const AbstractMetaEnum &e) +{ + d << '"' << e.fullName() << '"'; + if (e.isDeprecated()) + d << " (deprecated)"; + d << " \"" << e.underlyingType() << '"'; + if (!e.isSigned()) + d << " (unsigned)"; + d << " ["; + const AbstractMetaEnumValueList &values = e.values(); + const bool hasFlags = e.typeEntry()->flags() != nullptr; + for (qsizetype i = 0, count = values.size(); i < count; ++i) { + if (i) + d << ", "; + formatMetaEnumValue(d, values.at(i), hasFlags); + } + d << ']'; +} + +QDebug operator<<(QDebug d, const AbstractMetaEnum *ae) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaEnum("; + if (ae) + formatMetaEnum(d, *ae); + else + d << '0'; + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaEnum &ae) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaEnum("; + formatMetaEnum(d, ae); + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetaenum.h b/sources/shiboken6/ApiExtractor/abstractmetaenum.h new file mode 100644 index 000000000..03d7a3082 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetaenum.h @@ -0,0 +1,125 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETAENUM_H +#define ABSTRACTMETAENUM_H + +#include "abstractmetalang_typedefs.h" +#include "enclosingclassmixin.h" +#include "parser/codemodel_enums.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QSharedDataPointer> +#include <QtCore/QString> + +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class AbstractMetaEnumData; +class AbstractMetaEnumValueData; +class Documentation; +class EnumValue; +class EnumTypeEntry; + +class AbstractMetaEnumValue +{ +public: + AbstractMetaEnumValue(); + AbstractMetaEnumValue(const AbstractMetaEnumValue &); + AbstractMetaEnumValue &operator=(const AbstractMetaEnumValue &); + AbstractMetaEnumValue(AbstractMetaEnumValue &&) noexcept; + AbstractMetaEnumValue &operator=(AbstractMetaEnumValue &&) noexcept; + ~AbstractMetaEnumValue(); + + EnumValue value() const; + void setValue(EnumValue value); + + QString stringValue() const; + void setStringValue(const QString &v); + + QString name() const; + void setName(const QString &name); + + bool isDeprecated() const; + void setDeprecated(bool deprecated); + + Documentation documentation() const; + void setDocumentation(const Documentation& doc); + + int usedBits() const; + +private: + QSharedDataPointer<AbstractMetaEnumValueData> d; +}; + +class AbstractMetaEnum : public EnclosingClassMixin +{ +public: + AbstractMetaEnum(); + AbstractMetaEnum(const AbstractMetaEnum &); + AbstractMetaEnum &operator=(const AbstractMetaEnum &); + AbstractMetaEnum(AbstractMetaEnum &&) noexcept; + AbstractMetaEnum &operator=(AbstractMetaEnum &&) noexcept; + ~AbstractMetaEnum(); + + const AbstractMetaEnumValueList &values() const; + AbstractMetaEnumValueList nonRejectedValues() const; + void addEnumValue(const AbstractMetaEnumValue &enumValue); + + std::optional<AbstractMetaEnumValue> findEnumValue(QStringView value) const; + + QString name() const; + QString qualifiedCppName() const; + + Access access() const; + void setAccess(Access a); + bool isPrivate() const { return access() == Access::Private; } + bool isProtected() const { return access() == Access::Protected; } + + bool isDeprecated() const; + void setDeprecated(bool deprecated); + bool hasDeprecatedValues() const; + AbstractMetaEnumValueList deprecatedValues() const; + + const Documentation &documentation() const; + void setDocumentation(const Documentation& doc); + + QString qualifier() const; + + QString package() const; + + QString fullName() const; + + EnumKind enumKind() const; + void setEnumKind(EnumKind kind); + + bool isAnonymous() const; + + // Has the enum been declared inside a Q_ENUMS() macro in its enclosing class? + bool hasQEnumsDeclaration() const; + void setHasQEnumsDeclaration(bool on); + + EnumTypeEntryCPtr typeEntry() const; + void setTypeEntry(const EnumTypeEntryCPtr &entry); + + bool isSigned() const; + void setSigned(bool s); + + QString underlyingType() const; + void setUnderlyingType(const QString &underlyingType); + + static QString intTypeForSize(int usedBits, bool isSigned); + int usedBits() const; + +private: + QSharedDataPointer<AbstractMetaEnumData> d; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const AbstractMetaEnumValue &ae); +QDebug operator<<(QDebug d, const AbstractMetaEnum *ae); +QDebug operator<<(QDebug d, const AbstractMetaEnum &ae); +#endif + +#endif // ABSTRACTMETAENUM_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetafield.cpp b/sources/shiboken6/ApiExtractor/abstractmetafield.cpp new file mode 100644 index 000000000..27a76d04d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetafield.cpp @@ -0,0 +1,254 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetafield.h" +#include "abstractmetabuilder.h" +#include "abstractmetalang.h" +#include "abstractmetatype.h" +#include "documentation.h" +#include "modifications.h" +#include "complextypeentry.h" +#include "typesystemtypeentry.h" +#include "parser/codemodel.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> + +using namespace Qt::StringLiterals; + +class AbstractMetaFieldData : public QSharedData +{ +public: + QString m_originalName; + QString m_name; + AbstractMetaType m_type; + Documentation m_doc; + bool m_setterEnabled = true; // Modifications + bool m_getterEnabled = true; // Modifications + bool m_static = false; + Access m_access = Access::Public; +}; + +AbstractMetaField::AbstractMetaField() : d(new AbstractMetaFieldData) +{ +} + +AbstractMetaField::AbstractMetaField(const AbstractMetaField &) = default; +AbstractMetaField &AbstractMetaField::operator=(const AbstractMetaField &) = default; +AbstractMetaField::AbstractMetaField(AbstractMetaField &&) noexcept = default; +AbstractMetaField &AbstractMetaField::operator=(AbstractMetaField &&) noexcept = default; +AbstractMetaField::~AbstractMetaField() = default; + +// returned->setEnclosingClass(nullptr); + +std::optional<AbstractMetaField> + AbstractMetaField::find(const AbstractMetaFieldList &haystack, + QStringView needle) +{ + for (const auto &f : haystack) { + if (f.name() == needle) + return f; + } + return {}; +} + +/******************************************************************************* + * Indicates that this field has a modification that removes it + */ +bool AbstractMetaField::isModifiedRemoved() const +{ + const FieldModificationList &mods = modifications(); + for (const FieldModification &mod : mods) { + if (mod.isRemoved()) + return true; + } + + return false; +} + +bool AbstractMetaField::generateOpaqueContainer() const +{ + const FieldModificationList &mods = modifications(); + for (const FieldModification &mod : mods) { + if (mod.isOpaqueContainer()) + return true; + } + return false; +} + +const AbstractMetaType &AbstractMetaField::type() const +{ + return d->m_type; +} + +void AbstractMetaField::setType(const AbstractMetaType &type) +{ + if (d->m_type != type) + d->m_type = type; +} + +QString AbstractMetaField::name() const +{ + return d->m_name; +} + +void AbstractMetaField::setName(const QString &name) +{ + if (d->m_name != name) + d->m_name = name; +} + +Access AbstractMetaField::access() const +{ + return d->m_access; +} + +void AbstractMetaField::setAccess(Access a) +{ + if (a != d->m_access) + d->m_access = a; +} + +bool AbstractMetaField::isStatic() const +{ + return d->m_static; +} + +void AbstractMetaField::setStatic(bool s) +{ + if (s != d->m_static) + d->m_static = s; +} + +QString AbstractMetaField::qualifiedCppName() const +{ + return enclosingClass()->qualifiedCppName() + u"::"_s + + originalName(); +} + +QStringList AbstractMetaField::definitionNames() const +{ + return AbstractMetaBuilder::definitionNames(d->m_name, snakeCase()); +} + +QString AbstractMetaField::originalName() const +{ + return d->m_originalName.isEmpty() ? d->m_name : d->m_originalName; +} + +void AbstractMetaField::setOriginalName(const QString &name) +{ + if (d->m_originalName != name) + d->m_originalName = name; +} + +const Documentation &AbstractMetaField::documentation() const +{ + return d->m_doc; +} + +void AbstractMetaField::setDocumentation(const Documentation &doc) +{ + if (d->m_doc != doc) + d->m_doc = doc; +} + +bool AbstractMetaField::isGetterEnabled() const +{ + return d->m_getterEnabled; +} + +void AbstractMetaField::setGetterEnabled(bool e) +{ + if (d->m_getterEnabled != e) + d->m_getterEnabled = e; +} + +bool AbstractMetaField::isSetterEnabled() const +{ + return d->m_setterEnabled; +} + +void AbstractMetaField::setSetterEnabled(bool e) +{ + if (e != d->m_setterEnabled) + d->m_setterEnabled = e; +} + +bool AbstractMetaField::canGenerateGetter() const +{ + return d->m_getterEnabled && !isStatic() && !d->m_type.isArray(); +} + +bool AbstractMetaField::canGenerateSetter() const +{ + return d->m_setterEnabled && !isStatic() + && !d->m_type.isArray() + && (!d->m_type.isConstant() || d->m_type.isPointerToConst()); +} + +TypeSystem::SnakeCase AbstractMetaField::snakeCase() const +{ + // Renamed? + if (!d->m_originalName.isEmpty() && d->m_originalName != d->m_name) + return TypeSystem::SnakeCase::Disabled; + + for (const auto &mod : modifications()) { + if (mod.snakeCase() != TypeSystem::SnakeCase::Unspecified) + return mod.snakeCase(); + } + + auto typeEntry = enclosingClass()->typeEntry(); + const auto snakeCase = typeEntry->snakeCase(); + return snakeCase != TypeSystem::SnakeCase::Unspecified + ? snakeCase : typeSystemTypeEntry(typeEntry)->snakeCase(); +} + +FieldModificationList AbstractMetaField::modifications() const +{ + const FieldModificationList &mods = enclosingClass()->typeEntry()->fieldModifications(); + FieldModificationList returned; + + for (const FieldModification &mod : mods) { + if (mod.name() == name()) + returned += mod; + } + + return returned; +} + +#ifndef QT_NO_DEBUG_STREAM +void AbstractMetaField::formatDebug(QDebug &d) const +{ + if (isStatic()) + d << "static "; + d << access() << ' ' << type().name() << " \"" << name() << '"'; + +} + +QDebug operator<<(QDebug d, const AbstractMetaField *af) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaField("; + if (af) + af->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaField &af) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaField("; + af.formatDebug(d); + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetafield.h b/sources/shiboken6/ApiExtractor/abstractmetafield.h new file mode 100644 index 000000000..0fa858791 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetafield.h @@ -0,0 +1,88 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETAFIELD_H +#define ABSTRACTMETAFIELD_H + +#include "abstractmetalang_typedefs.h" +#include "parser/codemodel_enums.h" +#include "typesystem_enums.h" +#include "modifications_typedefs.h" +#include "typesystem_typedefs.h" +#include "enclosingclassmixin.h" + +#include <QtCore/QSharedDataPointer> + +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class Documentation; +class AbstractMetaFieldData; + +class AbstractMetaField : public EnclosingClassMixin +{ +public: + AbstractMetaField(); + AbstractMetaField(const AbstractMetaField &); + AbstractMetaField &operator=(const AbstractMetaField &); + AbstractMetaField(AbstractMetaField &&) noexcept; + AbstractMetaField &operator=(AbstractMetaField &&) noexcept; + ~AbstractMetaField(); + + FieldModificationList modifications() const; + + bool isModifiedRemoved() const; + bool generateOpaqueContainer() const; + + const AbstractMetaType &type() const; + void setType(const AbstractMetaType &type); + + QString name() const; + void setName(const QString &name); + + Access access() const; + void setAccess(Access a); + bool isPrivate() const { return access() == Access::Private; } + bool isProtected() const { return access() == Access::Protected; } + + bool isStatic() const; + void setStatic(bool s); + + QString qualifiedCppName() const; + + // Names under which the field will be registered to Python. + QStringList definitionNames() const; + + QString originalName() const; + void setOriginalName(const QString& name); + + const Documentation &documentation() const; + void setDocumentation(const Documentation& doc); + + bool isGetterEnabled() const; // Modifications + void setGetterEnabled(bool e); + bool isSetterEnabled() const; // Modifications + void setSetterEnabled(bool e); + + bool canGenerateGetter() const; + bool canGenerateSetter() const; + + TypeSystem::SnakeCase snakeCase() const; + + static std::optional<AbstractMetaField> + find(const AbstractMetaFieldList &haystack, QStringView needle); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif +private: + QSharedDataPointer<AbstractMetaFieldData> d; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const AbstractMetaField *af); +QDebug operator<<(QDebug d, const AbstractMetaField &af); +#endif + +#endif // ABSTRACTMETAFIELD_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp new file mode 100644 index 000000000..11a02f154 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.cpp @@ -0,0 +1,1707 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetafunction.h" +#include "abstractmetaargument.h" +#include "abstractmetabuilder.h" +#include "abstractmetalang.h" +#include "abstractmetalang_helpers.h" +#include "abstractmetatype.h" +#include "addedfunction.h" +#include <codemodel.h> +#include "documentation.h" +#include "exception.h" +#include "messages.h" +#include "codesnip.h" +#include "modifications.h" +#include "reporthandler.h" +#include "sourcelocation.h" +#include "typedatabase.h" +#include "complextypeentry.h" +#include "containertypeentry.h" +#include "functiontypeentry.h" +#include "primitivetypeentry.h" +#include "typesystemtypeentry.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QRegularExpression> + +#include <algorithm> + +using namespace Qt::StringLiterals; + +// Cache FunctionModificationList in a flat list per class (0 for global +// functions, or typically owner/implementing/declaring class. +struct ModificationCacheEntry +{ + AbstractMetaClassCPtr klass; + FunctionModificationList modifications; +}; + +using ModificationCache = QList<ModificationCacheEntry>; + +class AbstractMetaFunctionPrivate +{ +public: + AbstractMetaFunctionPrivate() + : m_constant(false), + m_reverse(false), + m_pointerOperator(false), + m_isCallOperator(false) + { + } + + QString signature() const; + QString formatMinimalSignature(const AbstractMetaFunction *q, + bool comment) const; + QString modifiedName(const AbstractMetaFunction *q) const; + int overloadNumber(const AbstractMetaFunction *q) const; + + const FunctionModificationList &modifications(const AbstractMetaFunction *q, + const AbstractMetaClassCPtr &implementor) const; + + bool applyTypeModification(const AbstractMetaFunction *q, + const QString &type, int number, QString *errorMessage); + + QString m_name; + QString m_originalName; + Documentation m_doc; + mutable QString m_cachedMinimalSignature; + mutable QString m_cachedSignature; + mutable QString m_cachedModifiedName; + QString m_unresolvedSignature; + + FunctionTypeEntryPtr m_typeEntry; + AbstractMetaFunction::FunctionType m_functionType = AbstractMetaFunction::NormalFunction; + AbstractMetaType m_type; + QString m_modifiedTypeName; + AbstractMetaClassCPtr m_class; + AbstractMetaClassCPtr m_implementingClass; + AbstractMetaClassCPtr m_declaringClass; + mutable ModificationCache m_modificationCache; + int m_propertySpecIndex = -1; + AbstractMetaArgumentList m_arguments; + AddedFunctionPtr m_addedFunction; + SourceLocation m_sourceLocation; + AbstractMetaFunction::Attributes m_attributes; + FunctionAttributes m_cppAttributes; + AbstractMetaFunction::Flags m_flags; + uint m_constant : 1; + uint m_reverse : 1; + uint m_pointerOperator : 1; + uint m_isCallOperator : 1; + mutable int m_cachedOverloadNumber = TypeSystem::OverloadNumberUnset; + Access m_access = Access::Public; + Access m_originalAccess = Access::Public; + ExceptionSpecification m_exceptionSpecification = ExceptionSpecification::Unknown; + TypeSystem::AllowThread m_allowThreadModification = TypeSystem::AllowThread::Unspecified; + TypeSystem::ExceptionHandling m_exceptionHandlingModification = TypeSystem::ExceptionHandling::Unspecified; +}; + +AbstractMetaFunction::AbstractMetaFunction(const QString &name) : + AbstractMetaFunction() +{ + d->m_originalName = d->m_name = name; +} + +AbstractMetaFunction::AbstractMetaFunction(const AddedFunctionPtr &addedFunc) : + AbstractMetaFunction(addedFunc->name()) +{ + d->m_addedFunction = addedFunc; + setConstant(addedFunc->isConstant()); + switch (addedFunc->access()) { + case AddedFunction::Protected: + setAccess(Access::Protected); + break; + case AddedFunction::Public: + setAccess(Access::Public); + break; + } + AbstractMetaFunction::Attributes atts; + if (addedFunc->isStatic()) + setCppAttribute(FunctionAttribute::Static); + if (addedFunc->isClassMethod()) + atts |= AbstractMetaFunction::ClassMethod; + setAttributes(atts); +} + +QString AbstractMetaFunction::name() const +{ + return d->m_name; +} + +void AbstractMetaFunction::setName(const QString &name) +{ + d->m_name = name; +} + +QString AbstractMetaFunction::originalName() const +{ + return d->m_originalName.isEmpty() ? name() : d->m_originalName; +} + +void AbstractMetaFunction::setOriginalName(const QString &name) +{ + d->m_originalName = name; +} + +Access AbstractMetaFunction::access() const +{ + return d->m_access; +} + +void AbstractMetaFunction::setAccess(Access a) +{ + d->m_originalAccess = d->m_access = a; +} + +void AbstractMetaFunction::modifyAccess(Access a) +{ + d->m_access = a; +} + +bool AbstractMetaFunction::wasPrivate() const +{ + return d->m_originalAccess == Access::Private; +} + +bool AbstractMetaFunction::wasProtected() const +{ + return d->m_originalAccess == Access::Protected; +} + +bool AbstractMetaFunction::wasPublic() const +{ + return d->m_originalAccess == Access::Public; +} + +QStringList AbstractMetaFunction::definitionNames() const +{ + return AbstractMetaBuilder::definitionNames(d->m_name, snakeCase()); +} + +const Documentation &AbstractMetaFunction::documentation() const +{ + return d->m_doc; +} + +void AbstractMetaFunction::setDocumentation(const Documentation &doc) +{ + d->m_doc = doc; +} + +bool AbstractMetaFunction::isReverseOperator() const +{ + return d->m_reverse; +} + +void AbstractMetaFunction::setReverseOperator(bool reverse) +{ + d->m_reverse = reverse; +} + +bool AbstractMetaFunction::isPointerOperator() const +{ + return d->m_pointerOperator; +} + +void AbstractMetaFunction::setPointerOperator(bool value) +{ + d->m_pointerOperator = value; +} + +bool AbstractMetaFunction::isExplicit() const +{ + return d->m_cppAttributes.testFlag(FunctionAttribute::Explicit); +} + +void AbstractMetaFunction::setExplicit(bool isExplicit) +{ + d->m_cppAttributes.setFlag(FunctionAttribute::Explicit, isExplicit); +} + +bool AbstractMetaFunction::returnsBool() const +{ + if (d->m_type.typeUsagePattern() != AbstractMetaType::PrimitivePattern) + return false; + return basicReferencedTypeEntry(d->m_type.typeEntry())->name() == u"bool"; +} + +bool AbstractMetaFunction::isOperatorBool() const +{ + return d->m_functionType == AbstractMetaFunction::ConversionOperator + && d->m_constant && returnsBool(); +} + +AbstractMetaFunction::AbstractMetaFunction() : d(new AbstractMetaFunctionPrivate) +{ +} + +AbstractMetaFunction::~AbstractMetaFunction() = default; + +AbstractMetaFunction::Attributes AbstractMetaFunction::attributes() const +{ + return d->m_attributes; +} + +void AbstractMetaFunction::setAttributes(Attributes attributes) +{ + d->m_attributes = attributes; +} + +void AbstractMetaFunction::operator+=(AbstractMetaFunction::Attribute attribute) +{ + d->m_attributes.setFlag(attribute); +} + +void AbstractMetaFunction::operator-=(AbstractMetaFunction::Attribute attribute) +{ + d->m_attributes.setFlag(attribute, false); +} + +FunctionAttributes AbstractMetaFunction::cppAttributes() const +{ + return d->m_cppAttributes; +} + +void AbstractMetaFunction::setCppAttributes(FunctionAttributes a) +{ + d->m_cppAttributes = a; +} + +void AbstractMetaFunction::setCppAttribute(FunctionAttribute a, bool on) +{ + d->m_cppAttributes.setFlag(a, on); +} + +AbstractMetaFunction::Flags AbstractMetaFunction::flags() const +{ + return d->m_flags; +} + +void AbstractMetaFunction::setFlags(Flags f) +{ + d->m_flags = f; +} + +/******************************************************************************* + * Indicates that this function has a modification that removes it + */ +bool AbstractMetaFunction::isModifiedRemoved(AbstractMetaClassCPtr cls) const +{ + if (!isInGlobalScope() && !cls) + cls = d->m_implementingClass; + for (const auto &mod : modifications(cls)) { + if (mod.isRemoved()) + return true; + } + + return false; +} + +bool AbstractMetaFunction::isModifiedFinal(AbstractMetaClassCPtr cls) const +{ + if (!isInGlobalScope() && cls == nullptr) + cls = d->m_implementingClass; + for (const auto &mod : modifications(cls)) { + if (mod.modifiers().testFlag(FunctionModification::Final)) + return true; + } + return false; +} + +bool AbstractMetaFunction::isVoid() const +{ + return d->m_type.isVoid(); +} + +const AbstractMetaType &AbstractMetaFunction::type() const +{ + return d->m_type; +} + +void AbstractMetaFunction::setType(const AbstractMetaType &type) +{ + d->m_type = type; +} + +AbstractMetaClassCPtr AbstractMetaFunction::ownerClass() const +{ + return d->m_class; +} + +void AbstractMetaFunction::setOwnerClass(const AbstractMetaClassCPtr &cls) +{ + d->m_class = cls; +} + +bool AbstractMetaFunction::operator<(const AbstractMetaFunction &other) const +{ + return compareTo(&other) & NameLessThan; +} + + +/*! + Returns a mask of CompareResult describing how this function is + compares to another function +*/ +AbstractMetaFunction::CompareResult AbstractMetaFunction::compareTo(const AbstractMetaFunction *other) const +{ + CompareResult result; + + // Enclosing class... + if (ownerClass() == other->ownerClass()) + result |= EqualImplementor; + + // Attributes + if (attributes() == other->attributes() && cppAttributes() == other->cppAttributes()) + result |= EqualAttributes; + + // Compare types + if (type().name() == other->type().name()) + result |= EqualReturnType; + + // Compare names + int cmp = originalName().compare(other->originalName()); + + if (cmp < 0) + result |= NameLessThan; + else if (!cmp) + result |= EqualName; + + // compare name after modification... + cmp = modifiedName().compare(other->modifiedName()); + if (!cmp) + result |= EqualModifiedName; + + // Compare arguments... + AbstractMetaArgumentList minArguments; + AbstractMetaArgumentList maxArguments; + if (arguments().size() < other->arguments().size()) { + minArguments = arguments(); + maxArguments = other->arguments(); + } else { + minArguments = other->arguments(); + maxArguments = arguments(); + } + + const auto minCount = minArguments.size(); + const auto maxCount = maxArguments.size(); + bool same = true; + for (qsizetype i = 0; i < maxCount; ++i) { + if (i < minCount) { + const AbstractMetaArgument &min_arg = minArguments.at(i); + const AbstractMetaArgument &max_arg = maxArguments.at(i); + if (min_arg.type().name() != max_arg.type().name() + && (min_arg.defaultValueExpression().isEmpty() || max_arg.defaultValueExpression().isEmpty())) { + same = false; + break; + } + } else { + if (maxArguments.at(i).defaultValueExpression().isEmpty()) { + same = false; + break; + } + } + } + + if (same) + result |= minCount == maxCount ? EqualArguments : EqualDefaultValueOverload; + + return result; +} + +// Is this the const overload of another function of equivalent return type? +bool AbstractMetaFunction::isConstOverloadOf(const AbstractMetaFunction *other) const +{ + const auto argumentCount = d->m_arguments.size(); + if (!isConstant() || other->isConstant() || name() != other->name() + || argumentCount != other->arguments().size()) { + return false; + } + + // Match "const Foo &getFoo() const" / "Foo &getFoo()" / "Foo getFoo() const" + const auto otherType = other->type(); + if (d->m_type.name() != otherType.name() + || d->m_type.indirectionsV() != otherType.indirectionsV()) { + return false; + } + + const auto &otherArguments = other->arguments(); + for (qsizetype a = 0; a < argumentCount; ++a) { + if (d->m_arguments.at(a).type() != otherArguments.at(a).type()) + return false; + } + return true; +} + +AbstractMetaFunction *AbstractMetaFunction::copy() const +{ + auto *cpy = new AbstractMetaFunction; + cpy->setAttributes(attributes()); + auto ca = cppAttributes(); + // Historical bug: explicit was not copied! (causing nontypetemplate_test.py fail) + ca.setFlag(FunctionAttribute::Explicit, false); + cpy->setCppAttributes(ca); + cpy->setFlags(flags()); + cpy->setAccess(access()); + cpy->setName(name()); + cpy->setOriginalName(originalName()); + cpy->setOwnerClass(ownerClass()); + cpy->setImplementingClass(implementingClass()); + cpy->setFunctionType(functionType()); + cpy->setDeclaringClass(declaringClass()); + cpy->setType(type()); + cpy->setConstant(isConstant()); + cpy->setExceptionSpecification(d->m_exceptionSpecification); + cpy->setAllowThreadModification(d->m_allowThreadModification); + cpy->setExceptionHandlingModification(d->m_exceptionHandlingModification); + cpy->d->m_modifiedTypeName = d->m_modifiedTypeName; + cpy->d->m_addedFunction = d->m_addedFunction; + cpy->d->m_arguments = d->m_arguments; + + return cpy; +} + +bool AbstractMetaFunction::usesRValueReferences() const +{ + if (d->m_functionType == MoveConstructorFunction || d->m_functionType == MoveAssignmentOperatorFunction) + return true; + if (d->m_type.referenceType() == RValueReference) + return true; + for (const AbstractMetaArgument &a : d->m_arguments) { + if (a.type().referenceType() == RValueReference) + return true; + } + return false; +} + +bool AbstractMetaFunction::generateBinding() const +{ + switch (d->m_functionType) { + case ConversionOperator: + if (d->m_name != u"operator int" && d->m_name != u"operator double") + return false; + break; + case AssignmentOperatorFunction: + case MoveAssignmentOperatorFunction: + case AbstractMetaFunction::MoveConstructorFunction: + return false; + default: + if (!isWhiteListed()) + return false; + break; + } + // Can we access the wrapper in case of a protected method? If not, + // disable for consistency regardless of avoidProtectedHack. + if (isProtected()) { + const auto typeFlags = ownerClass()->typeEntry()->typeFlags(); + if (typeFlags.testFlag(ComplexTypeEntry::DisableWrapper)) + return false; + } + if (isPrivate() && d->m_functionType != EmptyFunction) + return false; + // RValue references only for user-specified + // functions (<add-function>/<declare-function>/<function>) + return d->m_name != u"qt_metacall" && + (!usesRValueReferences() || d->m_addedFunction || d->m_typeEntry) + && !isModifiedRemoved(); +} + +bool AbstractMetaFunction::isWhiteListed() const +{ + switch (d->m_functionType) { + case NormalFunction: + case SignalFunction: + case SlotFunction: + if (auto dc = declaringClass()) { + const QSet<QString> &whiteList = dc->typeEntry()->generateFunctions(); + return whiteList.isEmpty() || whiteList.contains(d->m_name) + || whiteList.contains(minimalSignature()); + } + break; + default: + break; + } + return true; +} + +QString AbstractMetaFunctionPrivate::signature() const +{ + if (m_cachedSignature.isEmpty()) { + m_cachedSignature = m_originalName; + + m_cachedSignature += u'('; + + for (qsizetype i = 0; i < m_arguments.size(); ++i) { + const AbstractMetaArgument &a = m_arguments.at(i); + const AbstractMetaType &t = a.type(); + if (i > 0) + m_cachedSignature += u", "_s; + m_cachedSignature += t.cppSignature(); + // We need to have the argument names in the qdoc files + m_cachedSignature += u' '; + m_cachedSignature += a.name(); + } + m_cachedSignature += u')'; + + if (m_constant) + m_cachedSignature += u" const"_s; + } + return m_cachedSignature; +} + +QString AbstractMetaFunction::signature() const +{ + return d->signature(); +} + +QString AbstractMetaFunction::classQualifiedSignature() const +{ + QString result; + if (d->m_implementingClass) + result += d->m_implementingClass->qualifiedCppName() + u"::"_s; + result += signature(); + return result; +} + +QString AbstractMetaFunction::unresolvedSignature() const +{ + return d->m_unresolvedSignature; +} + +void AbstractMetaFunction::setUnresolvedSignature(const QString &s) +{ + d->m_unresolvedSignature = s; +} + +bool AbstractMetaFunction::isConstant() const +{ + return d->m_constant; +} + +void AbstractMetaFunction::setConstant(bool constant) +{ + d->m_constant = constant; +} + +bool AbstractMetaFunction::isUserAdded() const +{ + return d->m_addedFunction && !d->m_addedFunction->isDeclaration(); +} + +bool AbstractMetaFunction::isUserAddedPythonOverride() const +{ + return d->m_addedFunction && d->m_addedFunction->isPythonOverride(); +} + +bool AbstractMetaFunction::isUserDeclared() const +{ + return d->m_addedFunction && d->m_addedFunction->isDeclaration(); +} + +int AbstractMetaFunction::actualMinimumArgumentCount() const +{ + int count = 0; + for (qsizetype i = 0, size = d->m_arguments.size(); i < size; ++i && ++count) { + const auto &arg = d->m_arguments.at(i); + if (arg.isModifiedRemoved()) + --count; + else if (!arg.defaultValueExpression().isEmpty()) + break; + } + + return count; +} + +int AbstractMetaFunction::actualArgumentIndex(int index) const +{ + if (index < 0 || index >= int(d->m_arguments.size())) + throw Exception(msgArgumentIndexOutOfRange(this, index)); + int result = 0; + for (int i = 0; i < index; ++i) { + if (!d->m_arguments.at(i).isModifiedRemoved()) + ++result; + } + return result; +} + +// Returns reference counts for argument at idx, or all arguments if idx == -2 +QList<ReferenceCount> AbstractMetaFunction::referenceCounts(const AbstractMetaClassCPtr &cls, int idx) const +{ + QList<ReferenceCount> returned; + + for (const auto &mod : modifications(cls)) { + for (const ArgumentModification &argumentMod : mod.argument_mods()) { + if (argumentMod.index() != idx && idx != -2) + continue; + returned += argumentMod.referenceCounts(); + } + } + + return returned; +} + +ArgumentOwner AbstractMetaFunction::argumentOwner(const AbstractMetaClassCPtr &cls, int idx) const +{ + for (const auto &mod : modifications(cls)) { + for (const ArgumentModification &argumentMod : mod.argument_mods()) { + if (argumentMod.index() != idx) + continue; + return argumentMod.owner(); + } + } + return ArgumentOwner(); +} + +QString AbstractMetaFunction::conversionRule(TypeSystem::Language language, int key) const +{ + for (const auto &modification : modifications(declaringClass())) { + for (const ArgumentModification &argumentModification : modification.argument_mods()) { + if (argumentModification.index() != key) + continue; + + for (const CodeSnip &snip : argumentModification.conversionRules()) { + if (snip.language == language && !snip.code().isEmpty()) + return snip.code(); + } + } + } + + return QString(); +} + +bool AbstractMetaFunction::hasConversionRule(TypeSystem::Language language, int idx) const +{ + return !conversionRule(language, idx).isEmpty(); +} + +// FIXME If we remove a arg. in the method at the base class, it will not reflect here. +bool AbstractMetaFunction::argumentRemoved(int key) const +{ + for (const auto &modification : modifications(declaringClass())) { + for (const ArgumentModification &argumentModification : modification.argument_mods()) { + if (argumentModification.index() == key) { + if (argumentModification.isRemoved()) + return true; + } + } + } + + return false; +} + +AbstractMetaClassCPtr AbstractMetaFunction::targetLangOwner() const +{ + return d->m_class && d->m_class->isInvisibleNamespace() + ? d->m_class->targetLangEnclosingClass() : d->m_class; +} + +AbstractMetaClassCPtr AbstractMetaFunction::declaringClass() const +{ + return d->m_declaringClass; +} + +void AbstractMetaFunction::setDeclaringClass(const AbstractMetaClassCPtr &cls) +{ + d->m_declaringClass = cls; +} + +AbstractMetaClassCPtr AbstractMetaFunction::implementingClass() const +{ + return d->m_implementingClass; +} + +void AbstractMetaFunction::setImplementingClass(const AbstractMetaClassCPtr &cls) +{ + d->m_implementingClass = cls; +} + +const AbstractMetaArgumentList &AbstractMetaFunction::arguments() const +{ + return d->m_arguments; +} + +AbstractMetaArgumentList &AbstractMetaFunction::arguments() +{ + return d->m_arguments; +} + +void AbstractMetaFunction::setArguments(const AbstractMetaArgumentList &arguments) +{ + d->m_arguments = arguments; +} + +void AbstractMetaFunction::addArgument(const AbstractMetaArgument &argument) +{ + d->m_arguments << argument; +} + +static bool modifiedDeprecated(const FunctionModification &mod) +{ + return mod.modifiers().testFlag(FunctionModification::Deprecated); +} + +static bool modifiedUndeprecated(const FunctionModification &mod) +{ + return mod.modifiers().testFlag(FunctionModification::Undeprecated); +} + +bool AbstractMetaFunction::isDeprecated() const +{ + const auto &mods = modifications(declaringClass()); + + return d->m_cppAttributes.testFlag(FunctionAttribute::Deprecated) + ? std::none_of(mods.cbegin(), mods.cend(), modifiedUndeprecated) + : std::any_of(mods.cbegin(), mods.cend(), modifiedDeprecated); +} + +bool AbstractMetaFunction::isConstructor() const +{ + return d->m_functionType == ConstructorFunction || d->m_functionType == CopyConstructorFunction + || d->m_functionType == MoveConstructorFunction; +} + +bool AbstractMetaFunction::isDefaultConstructor() const +{ + return d->m_functionType == ConstructorFunction + && (d->m_arguments.isEmpty() + || d->m_arguments.constFirst().hasDefaultValueExpression()); +} + +bool AbstractMetaFunction::needsReturnType() const +{ + switch (d->m_functionType) { + case AbstractMetaFunction::ConstructorFunction: + case AbstractMetaFunction::CopyConstructorFunction: + case AbstractMetaFunction::MoveConstructorFunction: + case AbstractMetaFunction::DestructorFunction: + return false; + default: + break; + } + return true; +} + +bool AbstractMetaFunction::isInGlobalScope() const +{ + return d->m_class == nullptr; +} + +AbstractMetaFunction::FunctionType AbstractMetaFunction::functionType() const +{ + return d->m_functionType; +} + +void AbstractMetaFunction::setFunctionType(AbstractMetaFunction::FunctionType type) +{ + d->m_functionType = type; +} + +std::optional<AbstractMetaFunction::ComparisonOperatorType> +AbstractMetaFunction::comparisonOperatorType() const +{ + if (d->m_functionType != ComparisonOperator) + return {}; + static const QHash<QString, ComparisonOperatorType> mapping = { + {u"operator=="_s, OperatorEqual}, + {u"operator!="_s, OperatorNotEqual}, + {u"operator<"_s, OperatorLess}, + {u"operator<="_s, OperatorLessEqual}, + {u"operator>"_s, OperatorGreater}, + {u"operator>="_s, OperatorGreaterEqual} + }; + const auto it = mapping.constFind(originalName()); + Q_ASSERT(it != mapping.constEnd()); + return it.value(); +} + +// Auto-detect whether a function should be wrapped into +// Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS, that is, temporarily release +// the GIL (global interpreter lock). Doing so is required for any thread-wait +// functions, anything that might call a virtual function (potentially +// reimplemented in Python), and recommended for lengthy I/O or similar. +// It has performance costs, though. +bool AbstractMetaFunction::autoDetectAllowThread() const +{ + // Disallow for simple getter functions. + return !maybeAccessor(); +} + +bool AbstractMetaFunction::maybeAccessor() const +{ + return d->m_functionType == NormalFunction && d->m_class != nullptr + && d->m_constant != 0 && !isVoid() && d->m_arguments.isEmpty(); +} + +SourceLocation AbstractMetaFunction::sourceLocation() const +{ + return d->m_sourceLocation; +} + +void AbstractMetaFunction::setSourceLocation(const SourceLocation &sourceLocation) +{ + d->m_sourceLocation = sourceLocation; +} + +static inline TypeSystem::AllowThread allowThreadMod(const AbstractMetaClassCPtr &klass) +{ + return klass->typeEntry()->allowThread(); +} + +static inline bool hasAllowThreadMod(const AbstractMetaClassCPtr &klass) +{ + return allowThreadMod(klass) != TypeSystem::AllowThread::Unspecified; +} + +bool AbstractMetaFunction::allowThread() const +{ + auto allowThreadModification = d->m_allowThreadModification; + // If there is no modification on the function, check for a base class. + if (d->m_class && allowThreadModification == TypeSystem::AllowThread::Unspecified) { + if (auto base = recurseClassHierarchy(d->m_class, hasAllowThreadMod)) + allowThreadModification = allowThreadMod(base); + } + + bool result = true; + switch (allowThreadModification) { + case TypeSystem::AllowThread::Disallow: + result = false; + break; + case TypeSystem::AllowThread::Allow: + break; + case TypeSystem::AllowThread::Auto: + result = autoDetectAllowThread(); + break; + case TypeSystem::AllowThread::Unspecified: + result = false; + break; + } + if (!result && ReportHandler::isDebug(ReportHandler::MediumDebug)) + qCInfo(lcShiboken).noquote() << msgDisallowThread(this); + return result; +} + +TypeSystem::Ownership AbstractMetaFunction::argumentTargetOwnership(const AbstractMetaClassCPtr &cls, int idx) const +{ + for (const auto &modification : modifications(cls)) { + for (const ArgumentModification &argumentModification : modification.argument_mods()) { + if (argumentModification.index() == idx) + return argumentModification.targetOwnerShip(); + } + } + + return TypeSystem::UnspecifiedOwnership; +} + +const QString &AbstractMetaFunction::modifiedTypeName() const +{ + return d->m_modifiedTypeName; +} + +bool AbstractMetaFunction::generateOpaqueContainerReturn() const +{ + if (!isTypeModified() || d->m_type.typeUsagePattern() != AbstractMetaType::ContainerPattern) + return false; + // Needs to be a reference to a container, allow by value only for spans + if (d->m_type.referenceType() != LValueReference) { + auto cte = std::static_pointer_cast<const ContainerTypeEntry>(d->m_type.typeEntry()); + if (cte->containerKind() != ContainerTypeEntry::SpanContainer) + return false; + } + return d->m_type.generateOpaqueContainerForGetter(d->m_modifiedTypeName); +} + +bool AbstractMetaFunction::isModifiedToArray(int argumentIndex) const +{ + for (const auto &modification : modifications(declaringClass())) { + for (const ArgumentModification &argumentModification : modification.argument_mods()) { + if (argumentModification.index() == argumentIndex && argumentModification.isArray()) + return true; + } + } + return false; +} + +// Note: The declaring class must be correctly set for this to work. +bool AbstractMetaFunctionPrivate::applyTypeModification(const AbstractMetaFunction *q, + const QString &type, + int number, QString *errorMessage) +{ + if (number < 0 || number > m_arguments.size()) { + *errorMessage = + msgTypeModificationFailed(type, number, q, + msgArgumentOutOfRange(number, 0, m_arguments.size())); + return false; + } + + // Modified return types may have unparseable types like Python tuples + if (number == 0) { + m_modifiedTypeName = type; + return true; + } + + auto typeOpt = AbstractMetaType::fromString(type, errorMessage); + if (!typeOpt.has_value()) { + *errorMessage = msgTypeModificationFailed(type, number, q, *errorMessage); + return false; + } + m_arguments[number - 1].setModifiedType(typeOpt.value()); + return true; +} + +void AbstractMetaFunction::applyTypeModifications() +{ + QString errorMessage; + for (const auto &modification : modifications(declaringClass())) { + for (const ArgumentModification &am : modification.argument_mods()) { + const int n = am.index(); + if (am.isTypeModified() + && !d->applyTypeModification(this, am.modifiedType(), + n, &errorMessage)) { + throw Exception(errorMessage); + } else if (am.isRemoved() && n != 0) { + if (n < 1 || n > d->m_arguments.size()) { + errorMessage = + msgArgumentRemovalFailed(this, n, + msgArgumentOutOfRange(n, 1, d->m_arguments.size())); + throw Exception(errorMessage); + } + d->m_arguments[n - 1].setModifiedRemoved(true); + } + } + } +} + +QString AbstractMetaFunction::pyiTypeReplaced(int argumentIndex) const +{ + for (const auto &modification : modifications(declaringClass())) { + for (const ArgumentModification &argumentModification : modification.argument_mods()) { + if (argumentModification.index() == argumentIndex) { + QString type = argumentModification.pyiType(); + if (!type.isEmpty()) + return type; + type = argumentModification.modifiedType(); + if (!type.isEmpty()) + return type; + } + } + } + + return {}; +} + +// Parameter 'comment' indicates usage as a code comment of the overload decisor +QString AbstractMetaFunctionPrivate::formatMinimalSignature(const AbstractMetaFunction *q, + bool comment) const +{ + QString result = m_originalName + u'('; + for (qsizetype i = 0; i < m_arguments.size(); ++i) { + const auto &argument = m_arguments.at(i); + if (i > 0) + result += u','; + + const auto &type = comment ? argument.modifiedType() : argument.type(); + result += type.minimalSignature(); + if (comment && argument.hasDefaultValueExpression()) + result += u'='; + } + result += u')'; + if (m_constant) + result += u"const"_s; + result = TypeDatabase::normalizedSignature(result); + + if (comment && !q->isVoid()) { + result += u"->"_s; + result += q->isTypeModified() + ? q->modifiedTypeName() : q->type().minimalSignature(); + } + return result; +} + +QString AbstractMetaFunction::minimalSignature() const +{ + if (d->m_cachedMinimalSignature.isEmpty()) + d->m_cachedMinimalSignature = d->formatMinimalSignature(this, false); + return d->m_cachedMinimalSignature; +} + +QStringList AbstractMetaFunction::modificationSignatures() const +{ + QStringList result{minimalSignature()}; + if (d->m_unresolvedSignature != result.constFirst()) + result.append(d->m_unresolvedSignature); + return result; +} + +QString AbstractMetaFunction::signatureComment() const +{ + return d->formatMinimalSignature(this, true); +} + +QString AbstractMetaFunction::debugSignature() const +{ + QString result; + const auto attributes = cppAttributes(); + const bool isOverride = attributes.testFlag(FunctionAttribute::Override); + const bool isFinal = attributes.testFlag(FunctionAttribute::Final); + if (!isOverride && !isFinal && (attributes.testFlag(FunctionAttribute::Virtual))) + result += u"virtual "_s; + if (d->m_implementingClass) + result += d->m_implementingClass->qualifiedCppName() + u"::"_s; + result += minimalSignature(); + if (isOverride) + result += u" override"_s; + if (isFinal) + result += u" final"_s; + return result; +} + +FunctionModificationList AbstractMetaFunction::findClassModifications(const AbstractMetaFunction *f, + AbstractMetaClassCPtr implementor) +{ + const auto signatures = f->modificationSignatures(); + FunctionModificationList mods; + while (implementor) { + mods += implementor->typeEntry()->functionModifications(signatures); + if ((implementor == implementor->baseClass()) || + (implementor == f->implementingClass() && !mods.isEmpty())) { + break; + } + implementor = implementor->baseClass(); + } + return mods; +} + +FunctionModificationList AbstractMetaFunction::findGlobalModifications(const AbstractMetaFunction *f) +{ + auto *td = TypeDatabase::instance(); + return td->globalFunctionModifications(f->modificationSignatures()); +} + +const FunctionModificationList & + AbstractMetaFunctionPrivate::modifications(const AbstractMetaFunction *q, + const AbstractMetaClassCPtr &implementor) const +{ + if (m_addedFunction) + return m_addedFunction->modifications(); + for (const auto &ce : m_modificationCache) { + if (ce.klass == implementor) + return ce.modifications; + } + auto modifications = m_class == nullptr + ? AbstractMetaFunction::findGlobalModifications(q) + : AbstractMetaFunction::findClassModifications(q, implementor); + + m_modificationCache.append({implementor, modifications}); + return m_modificationCache.constLast().modifications; +} + +const FunctionModificationList & + AbstractMetaFunction::modifications(AbstractMetaClassCPtr implementor) const +{ + if (!implementor) + implementor = d->m_class; + return d->modifications(this, implementor); +} + +void AbstractMetaFunction::clearModificationsCache() +{ + d->m_modificationCache.clear(); +} + +const DocModificationList AbstractMetaFunction::addedFunctionDocModifications() const +{ + return d->m_addedFunction + ? d->m_addedFunction->docModifications() : DocModificationList{}; +} + +QString AbstractMetaFunction::argumentName(int index, + bool /* create */, + AbstractMetaClassCPtr /* implementor */) const +{ + return d->m_arguments[--index].name(); +} + +int AbstractMetaFunction::propertySpecIndex() const +{ + return d->m_propertySpecIndex; +} + +void AbstractMetaFunction::setPropertySpecIndex(int i) +{ + d->m_propertySpecIndex = i; +} + +FunctionTypeEntryPtr AbstractMetaFunction::typeEntry() const +{ + return d->m_typeEntry; +} + +void AbstractMetaFunction::setTypeEntry(const FunctionTypeEntryPtr &typeEntry) +{ + d->m_typeEntry = typeEntry; +} + +QString AbstractMetaFunction::targetLangPackage() const +{ + if (d->m_addedFunction != nullptr) + return d->m_addedFunction->targetLangPackage(); + if (d->m_class != nullptr) + return d->m_class->typeEntry()->targetLangPackage(); + if (d->m_typeEntry != nullptr) + return d->m_typeEntry->targetLangPackage(); + return {}; +} + +bool AbstractMetaFunction::isCallOperator() const +{ + return d->m_name == u"operator()"; +} + +bool AbstractMetaFunction::hasInjectedCode() const +{ + const FunctionModificationList &mods = modifications(ownerClass()); + for (const FunctionModification &mod : mods) { + if (mod.isCodeInjection()) + return true; + } + return false; +} + +// Traverse the code snippets, return true if predicate returns true +template <class Predicate> +bool AbstractMetaFunction::traverseCodeSnips(Predicate predicate, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + for (const FunctionModification &mod : modifications(ownerClass())) { + if (mod.isCodeInjection()) { + for (const CodeSnip &snip : mod.snips()) { + if ((snip.language & language) != 0 + && (snip.position == position || position == TypeSystem::CodeSnipPositionAny) + && predicate(snip)) { + return true; + } + } + } + } + return false; +} + +CodeSnipList AbstractMetaFunction::injectedCodeSnips(TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + CodeSnipList result; + traverseCodeSnips([&result] (const CodeSnip &s) { + result.append(s); + return false; + }, position, language); + return result; +} + +bool AbstractMetaFunction::injectedCodeContains(const QRegularExpression &pattern, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + return traverseCodeSnips([pattern] (const CodeSnip &s) { + return s.code().contains(pattern); + }, position, language); +} + +bool AbstractMetaFunction::injectedCodeContains(QStringView pattern, + TypeSystem::CodeSnipPosition position, + TypeSystem::Language language) const +{ + return traverseCodeSnips([pattern] (const CodeSnip &s) { + return s.code().contains(pattern); + }, position, language); +} + +bool AbstractMetaFunction::hasSignatureModifications() const +{ + const FunctionModificationList &mods = modifications(); + for (const FunctionModification &mod : mods) { + if (mod.isRenameModifier()) + return true; + for (const ArgumentModification &argmod : mod.argument_mods()) { + // since zero represents the return type and we're + // interested only in checking the function arguments, + // it will be ignored. + if (argmod.index() > 0) + return true; + } + } + return false; +} + +bool AbstractMetaFunction::isConversionOperator(const QString &funcName) +{ + return funcName.startsWith(u"operator "); +} + +ExceptionSpecification AbstractMetaFunction::exceptionSpecification() const +{ + return d->m_exceptionSpecification; +} + +void AbstractMetaFunction::setExceptionSpecification(ExceptionSpecification e) +{ + d->m_exceptionSpecification = e; +} + +static inline TypeSystem::ExceptionHandling exceptionMod(const AbstractMetaClassCPtr &klass) +{ + return klass->typeEntry()->exceptionHandling(); +} + +static inline bool hasExceptionMod(const AbstractMetaClassCPtr &klass) +{ + return exceptionMod(klass) != TypeSystem::ExceptionHandling::Unspecified; +} + +bool AbstractMetaFunction::generateExceptionHandling() const +{ + switch (d->m_functionType) { + case AbstractMetaFunction::CopyConstructorFunction: + case AbstractMetaFunction::MoveConstructorFunction: + case AbstractMetaFunction::AssignmentOperatorFunction: + case AbstractMetaFunction::MoveAssignmentOperatorFunction: + case AbstractMetaFunction::DestructorFunction: + return false; + default: + break; + } + + auto exceptionHandlingModification = d->m_exceptionHandlingModification; + // If there is no modification on the function, check for a base class. + if (d->m_class && exceptionHandlingModification == TypeSystem::ExceptionHandling::Unspecified) { + if (auto base = recurseClassHierarchy(d->m_class, hasExceptionMod)) + exceptionHandlingModification = exceptionMod(base); + } + + bool result = false; + switch (exceptionHandlingModification) { + case TypeSystem::ExceptionHandling::On: + result = true; + break; + case TypeSystem::ExceptionHandling::AutoDefaultToOn: + result = d->m_exceptionSpecification != ExceptionSpecification::NoExcept; + break; + case TypeSystem::ExceptionHandling::AutoDefaultToOff: + result = d->m_exceptionSpecification == ExceptionSpecification::Throws; + break; + case TypeSystem::ExceptionHandling::Unspecified: + case TypeSystem::ExceptionHandling::Off: + break; + } + return result; +} + +bool AbstractMetaFunction::isConversionOperator() const +{ + return d->m_functionType == ConversionOperator; +} + +bool AbstractMetaFunction::isOperatorOverload(const QString &funcName) +{ + if (isConversionOperator(funcName)) + return true; + + static const QRegularExpression opRegEx(u"^operator([+\\-\\*/%=&\\|\\^\\<>!][=]?" + "|\\+\\+|\\-\\-|&&|\\|\\||<<[=]?|>>[=]?|~" + "|\\[\\]|\\s+delete\\[?\\]?" + "|\\(\\)" + "|\\s+new\\[?\\]?)$"_s); + Q_ASSERT(opRegEx.isValid()); + return opRegEx.match(funcName).hasMatch(); +} + +bool AbstractMetaFunction::isOperatorOverload() const +{ + return d->m_functionType == AssignmentOperatorFunction + || (d->m_functionType >= FirstOperator && d->m_functionType <= LastOperator); +} + +bool AbstractMetaFunction::isArithmeticOperator() const +{ + return d->m_functionType == ArithmeticOperator; +} + +bool AbstractMetaFunction::isBitwiseOperator() const +{ + return d->m_functionType == BitwiseOperator + || d->m_functionType == ShiftOperator; +} + +bool AbstractMetaFunction::isComparisonOperator() const +{ + return d->m_functionType == ComparisonOperator; +} + +bool AbstractMetaFunction::isSymmetricalComparisonOperator() const +{ + if (d->m_functionType != ComparisonOperator || d->m_class == nullptr) + return false; + AbstractMetaType classType(d->m_class->typeEntry()); + classType.decideUsagePattern(); + return std::all_of(d->m_arguments.constBegin(), d->m_arguments.constEnd(), + [classType](const AbstractMetaArgument &a) { + return a.type().isEquivalent(classType);}); +} + +bool AbstractMetaFunction::isIncDecrementOperator() const +{ + return d->m_functionType == IncrementOperator + || d->m_functionType == DecrementOperator; +} +bool AbstractMetaFunction::isLogicalOperator() const +{ + return d->m_functionType == LogicalOperator; +} + +bool AbstractMetaFunction::isAssignmentOperator() const +{ + return d->m_functionType == AssignmentOperatorFunction + || d->m_functionType == MoveAssignmentOperatorFunction; +} + +bool AbstractMetaFunction::isGetter() const +{ + return d->m_functionType == NormalFunction && !isVoid() + && d->m_constant && d->m_access == Access::Public + && d->m_arguments.isEmpty(); +} + +bool AbstractMetaFunction::isQtIsNullMethod() const +{ + return isGetter() && d->m_name == u"isNull" && returnsBool(); +} + +int AbstractMetaFunction::arityOfOperator() const +{ + if (!isOperatorOverload() || isCallOperator()) + return -1; + + int arity = d->m_arguments.size(); + + // Operator overloads that are class members + // implicitly includes the instance and have + // one parameter less than their arity, + // so we increment it. + if (ownerClass() && arity < 2) + arity++; + + return arity; +} + +bool AbstractMetaFunction::isInplaceOperator() const +{ + static const QSet<QStringView> inplaceOperators = + { + u"operator+=", u"operator&=", u"operator-=", u"operator|=", + u"operator*=", u"operator^=", u"operator/=", u"operator<<=", + u"operator%=", u"operator>>=" + }; + + return isOperatorOverload() && inplaceOperators.contains(originalName()); +} + +bool AbstractMetaFunction::isVirtual() const +{ + return d->m_cppAttributes.testFlag(FunctionAttribute::Virtual); +} + +QString AbstractMetaFunctionPrivate::modifiedName(const AbstractMetaFunction *q) const +{ + if (m_cachedModifiedName.isEmpty()) { + for (const auto &mod : q->modifications(q->implementingClass())) { + if (mod.isRenameModifier()) { + m_cachedModifiedName = mod.renamedToName(); + break; + } + } + if (m_cachedModifiedName.isEmpty()) + m_cachedModifiedName = m_name; + } + return m_cachedModifiedName; +} + +QString AbstractMetaFunction::modifiedName() const +{ + return d->modifiedName(this); +} + +AbstractMetaFunctionCPtr +AbstractMetaFunction::find(const AbstractMetaFunctionCList &haystack, + QAnyStringView needle) +{ + for (const auto &f : haystack) { + if (f->name() == needle) + return f; + } + return {}; +} + +bool AbstractMetaFunction::matches(OperatorQueryOptions query) const +{ + bool result = false; + switch (d->m_functionType) { + case AbstractMetaFunction::AssignmentOperatorFunction: + result = query.testFlag(OperatorQueryOption::AssignmentOp); + break; + case AbstractMetaFunction::ConversionOperator: + result = query.testFlag(OperatorQueryOption::ConversionOp); + break; + case AbstractMetaFunction::ArithmeticOperator: + result = query.testFlag(OperatorQueryOption::ArithmeticOp); + break; + case AbstractMetaFunction::IncrementOperator: + case AbstractMetaFunction::DecrementOperator: + result = query.testFlag(OperatorQueryOption::IncDecrementOp); + break; + case AbstractMetaFunction::BitwiseOperator: + case AbstractMetaFunction::ShiftOperator: + result = query.testFlag(OperatorQueryOption::BitwiseOp); + break; + case AbstractMetaFunction::LogicalOperator: + result = query.testFlag(OperatorQueryOption::LogicalOp); + break; + case AbstractMetaFunction::SubscriptOperator: + result = query.testFlag(OperatorQueryOption::SubscriptionOp); + break; + case AbstractMetaFunction::ComparisonOperator: + result = query.testFlag(OperatorQueryOption::ComparisonOp); + if (!result && query.testFlag(OperatorQueryOption::SymmetricalComparisonOp)) + result = isSymmetricalComparisonOperator(); + break; + default: + break; + } + return result; +} + +void AbstractMetaFunction::setAllowThreadModification(TypeSystem::AllowThread am) +{ + d->m_allowThreadModification = am; +} + +void AbstractMetaFunction::setExceptionHandlingModification(TypeSystem::ExceptionHandling em) +{ + d->m_exceptionHandlingModification = em; +} + +int AbstractMetaFunctionPrivate::overloadNumber(const AbstractMetaFunction *q) const +{ + if (m_cachedOverloadNumber == TypeSystem::OverloadNumberUnset) { + m_cachedOverloadNumber = TypeSystem::OverloadNumberDefault; + for (const auto &mod : q->modifications(q->implementingClass())) { + if (mod.overloadNumber() != TypeSystem::OverloadNumberUnset) { + m_cachedOverloadNumber = mod.overloadNumber(); + break; + } + } + } + return m_cachedOverloadNumber; +} + +int AbstractMetaFunction::overloadNumber() const +{ + return d->overloadNumber(this); +} + +TypeSystem::SnakeCase AbstractMetaFunction::snakeCase() const +{ + if (isUserAdded()) + return TypeSystem::SnakeCase::Disabled; + // Renamed? + if (!d->m_originalName.isEmpty() && d->m_originalName != d->m_name) + return TypeSystem::SnakeCase::Disabled; + switch (d->m_functionType) { + case AbstractMetaFunction::NormalFunction: + case AbstractMetaFunction::SignalFunction: + case AbstractMetaFunction::EmptyFunction: + case AbstractMetaFunction::SlotFunction: + break; + default: + return TypeSystem::SnakeCase::Disabled; + } + + for (const auto &mod : modifications()) { + if (mod.snakeCase() != TypeSystem::SnakeCase::Unspecified) + return mod.snakeCase(); + } + + if (d->m_typeEntry) // Global function + return typeSystemTypeEntry(d->m_typeEntry)->snakeCase(); + + if (d->m_class) { + auto typeEntry = d->m_class->typeEntry(); + const auto snakeCase = typeEntry->snakeCase(); + return snakeCase != TypeSystem::SnakeCase::Unspecified + ? snakeCase : typeSystemTypeEntry(typeEntry)->snakeCase(); + } + return TypeSystem::SnakeCase::Disabled; +} + +// Query functions for generators +bool AbstractMetaFunction::injectedCodeUsesPySelf() const +{ + return injectedCodeContains(u"%PYSELF", TypeSystem::CodeSnipPositionAny, TypeSystem::NativeCode); +} + +bool AbstractMetaFunction::injectedCodeCallsPythonOverride() const +{ + static const QRegularExpression + overrideCallRegexCheck(R"(PyObject_Call\s*\(\s*%PYTHON_METHOD_OVERRIDE\s*,)"_L1); + Q_ASSERT(overrideCallRegexCheck.isValid()); + return injectedCodeContains(overrideCallRegexCheck, TypeSystem::CodeSnipPositionAny, + TypeSystem::NativeCode); +} + +bool AbstractMetaFunction::injectedCodeHasReturnValueAttribution(TypeSystem::Language language) const +{ + if (language == TypeSystem::TargetLangCode) { + static const QRegularExpression + retValAttributionRegexCheck_target(R"(%PYARG_0\s*=[^=]\s*.+)"_L1); + Q_ASSERT(retValAttributionRegexCheck_target.isValid()); + return injectedCodeContains(retValAttributionRegexCheck_target, TypeSystem::CodeSnipPositionAny, language); + } + + static const QRegularExpression + retValAttributionRegexCheck_native(R"(%0\s*=[^=]\s*.+)"_L1); + Q_ASSERT(retValAttributionRegexCheck_native.isValid()); + return injectedCodeContains(retValAttributionRegexCheck_native, TypeSystem::CodeSnipPositionAny, language); +} + +bool AbstractMetaFunction::injectedCodeUsesArgument(int argumentIndex) const +{ + const QRegularExpression argRegEx = CodeSnipAbstract::placeHolderRegex(argumentIndex + 1); + + return traverseCodeSnips([argRegEx](const CodeSnip &s) { + const QString code = s.code(); + return code.contains(u"%ARGUMENT_NAMES") || code.contains(argRegEx); + }, TypeSystem::CodeSnipPositionAny); +} + +bool AbstractMetaFunction::isVisibilityModifiedToPrivate() const +{ + for (const auto &mod : modifications()) { + if (mod.modifiers().testFlag(FunctionModification::Private)) + return true; + } + return false; +} + +struct ComparisonOperator +{ + const char *cppOperator; + const char *pythonOpCode; +}; + +using ComparisonOperatorMapping = + QHash<AbstractMetaFunction::ComparisonOperatorType, ComparisonOperator>; + +static const ComparisonOperatorMapping &comparisonOperatorMapping() +{ + static const ComparisonOperatorMapping result = { + {AbstractMetaFunction::OperatorEqual, {"==", "Py_EQ"}}, + {AbstractMetaFunction::OperatorNotEqual, {"!=", "Py_NE"}}, + {AbstractMetaFunction::OperatorLess, {"<", "Py_LT"}}, + {AbstractMetaFunction::OperatorLessEqual, {"<=", "Py_LE"}}, + {AbstractMetaFunction::OperatorGreater, {">", "Py_GT"}}, + {AbstractMetaFunction::OperatorGreaterEqual, {">=", "Py_GE"}} + }; + return result; +} + +const char * AbstractMetaFunction::pythonRichCompareOpCode(ComparisonOperatorType ct) +{ + return comparisonOperatorMapping().value(ct).pythonOpCode; +} + +const char * AbstractMetaFunction::cppComparisonOperator(ComparisonOperatorType ct) +{ + return comparisonOperatorMapping().value(ct).cppOperator; +} + +#ifndef QT_NO_DEBUG_STREAM +void AbstractMetaFunction::formatDebugBrief(QDebug &debug) const +{ + debug << '"' << debugSignature() << '"'; +} + +void AbstractMetaFunction::formatDebugVerbose(QDebug &debug) const +{ + debug << d->m_functionType << ' '; + if (d->m_class) + debug << d->m_access << ' '; + debug << d->m_type << ' ' << d->m_name; + switch (d->m_exceptionSpecification) { + case ExceptionSpecification::Unknown: + break; + case ExceptionSpecification::NoExcept: + debug << " noexcept"; + break; + case ExceptionSpecification::Throws: + debug << " throw(...)"; + break; + } + if (d->m_exceptionHandlingModification != TypeSystem::ExceptionHandling::Unspecified) + debug << " exeption-mod " << int(d->m_exceptionHandlingModification); + debug << '('; + for (qsizetype i = 0, count = d->m_arguments.size(); i < count; ++i) { + if (i) + debug << ", "; + debug << d->m_arguments.at(i); + } + const QString signature = minimalSignature(); + debug << "), signature=\"" << signature << '"'; + if (signature != d->m_unresolvedSignature) + debug << ", unresolvedSignature=\"" << d->m_unresolvedSignature << '"'; + if (d->m_constant) + debug << " [const]"; + if (d->m_reverse) + debug << " [reverse]"; + if (isUserAdded()) + debug << " [userAdded]"; + if (isUserDeclared()) + debug << " [userDeclared]"; + if (d->m_cppAttributes.testFlag(FunctionAttribute::Explicit)) + debug << " [explicit]"; + if (d->m_cppAttributes.testFlag(FunctionAttribute::Deprecated)) + debug << " [deprecated]"; + if (d->m_pointerOperator) + debug << " [operator->]"; + if (d->m_isCallOperator) + debug << " [operator()]"; + if (d->m_class) + debug << " class: " << d->m_class->name(); + if (d->m_implementingClass) + debug << " implementing class: " << d->m_implementingClass->name(); + if (d->m_declaringClass) + debug << " declaring class: " << d->m_declaringClass->name(); +} + +QDebug operator<<(QDebug debug, const AbstractMetaFunction *af) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "AbstractMetaFunction("; + if (af) { + if (debug.verbosity() > 2) { + af->formatDebugVerbose(debug); + } else { + debug << "signature="; + af->formatDebugBrief(debug); + } + } else { + debug << '0'; + } + debug << ')'; + return debug; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetafunction.h b/sources/shiboken6/ApiExtractor/abstractmetafunction.h new file mode 100644 index 000000000..e252e439d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetafunction.h @@ -0,0 +1,487 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETAFUNCTION_H +#define ABSTRACTMETAFUNCTION_H + +#include "abstractmetalang_enums.h" +#include "abstractmetalang_typedefs.h" +#include "typesystem_enums.h" +#include "modifications_typedefs.h" +#include "typesystem_typedefs.h" +#include "parser/codemodel_enums.h" + +#include <QtCore/QMetaObject> +#include <QtCore/QScopedPointer> + +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QRegularExpression) + +class AbstractMetaFunctionPrivate; +class AbstractMetaType; +class FunctionTypeEntry; +class Documentation; +class SourceLocation; + +struct ArgumentOwner; +struct ReferenceCount; + +class AbstractMetaFunction +{ + Q_GADGET +public: + Q_DISABLE_COPY_MOVE(AbstractMetaFunction) + + enum FunctionType { + ConstructorFunction, + CopyConstructorFunction, + MoveConstructorFunction, + AssignmentOperatorFunction, + MoveAssignmentOperatorFunction, + DestructorFunction, + NormalFunction, + SignalFunction, + EmptyFunction, + SlotFunction, + GetAttroFunction, + SetAttroFunction, + CallOperator, + FirstOperator = CallOperator, + ConversionOperator, + DereferenceOperator, // Iterator's operator * + ReferenceOperator, // operator & + ArrowOperator, + ArithmeticOperator, + IncrementOperator, + DecrementOperator, + BitwiseOperator, + LogicalOperator, + ShiftOperator, + SubscriptOperator, + ComparisonOperator, + LastOperator = ComparisonOperator + }; + Q_ENUM(FunctionType) + + enum ComparisonOperatorType { + OperatorEqual, OperatorNotEqual, OperatorLess, OperatorLessEqual, + OperatorGreater, OperatorGreaterEqual + }; + Q_ENUM(ComparisonOperatorType) + + enum CompareResultFlag { + EqualName = 0x00000001, + EqualArguments = 0x00000002, + EqualAttributes = 0x00000004, + EqualImplementor = 0x00000008, + EqualReturnType = 0x00000010, + EqualDefaultValueOverload = 0x00000020, + EqualModifiedName = 0x00000040, + + NameLessThan = 0x00001000, + + PrettySimilar = EqualName | EqualArguments, + Equal = 0x0000001f, + NotEqual = 0x00001000 + }; + Q_DECLARE_FLAGS(CompareResult, CompareResultFlag) + Q_FLAG(CompareResultFlag) + + enum Attribute { + None = 0x00000000, + + ClassMethod = 0x00000008, + + GetterFunction = 0x00000020, + SetterFunction = 0x00000040, + + PropertyReader = 0x00000100, + PropertyWriter = 0x00000200, + PropertyResetter = 0x00000400, + PropertyNotify = 0x00000800, + + // Add by meta builder (implicit constructors, inherited methods, etc) + AddedMethod = 0x001000000, + }; + Q_DECLARE_FLAGS(Attributes, Attribute) + Q_FLAG(Attribute) + + Attributes attributes() const; + void setAttributes(Attributes attributes); + + void operator+=(Attribute attribute); + void operator-=(Attribute attribute); + + FunctionAttributes cppAttributes() const; + void setCppAttributes(FunctionAttributes a); + void setCppAttribute(FunctionAttribute a, bool on = true); + + enum class Flag { // Internal flags not relevant for comparing functions + // Binary operator whose leading/trailing argument was removed by metabuilder + OperatorLeadingClassArgumentRemoved = 0x1, + OperatorTrailingClassArgumentRemoved = 0x2, + OperatorClassArgumentByValue = 0x4, // The removed class argument was passed by value + InheritedFromTemplate = 0x8, // Inherited from a template in metabuilder + HiddenFriend = 0x10, + PrivateSignal = 0x20 // Private Qt signal (cannot emit from client code) + }; + Q_DECLARE_FLAGS(Flags, Flag) + + Flags flags() const; + void setFlags(Flags f); + + bool isAbstract() const; + bool isClassMethod() const; + bool isStatic() const; + bool isPropertyReader() const; + bool isPropertyWriter() const; + bool isPropertyResetter() const; + + AbstractMetaFunction(); + explicit AbstractMetaFunction(const QString &name); + explicit AbstractMetaFunction(const AddedFunctionPtr &addedFunc); + ~AbstractMetaFunction(); + + QString name() const; + void setName(const QString &name); + + // Names under which the function will be registered to Python. + QStringList definitionNames() const; + + QString originalName() const; + + void setOriginalName(const QString &name); + + Access access() const; + void setAccess(Access a); + void modifyAccess(Access a); + + bool isPrivate() const { return access() == Access::Private; } + bool isProtected() const { return access() == Access::Protected; } + bool isPublic() const { return access() == Access::Public; } + bool wasPrivate() const; + bool wasProtected() const; + bool wasPublic() const; + + const Documentation &documentation() const; + void setDocumentation(const Documentation& doc); + + bool isReverseOperator() const; + void setReverseOperator(bool reverse); + + /// Returns true if this is a binary operator and the "self" operand is a + /// pointer, e.g. class Foo {}; operator+(SomeEnum, Foo*); + /// (not to be mixed up with DereferenceOperator). + bool isPointerOperator() const; + void setPointerOperator(bool value); + + + /** + * Says if the function (a constructor) was declared as explicit in C++. + * \return true if the function was declared as explicit in C++ + */ + bool isExplicit() const; + void setExplicit(bool isExplicit); + + bool returnsBool() const; + bool isOperatorBool() const; + static bool isConversionOperator(const QString& funcName); + + ExceptionSpecification exceptionSpecification() const; + void setExceptionSpecification(ExceptionSpecification e); + + bool generateExceptionHandling() const; + + bool isConversionOperator() const; + + static bool isOperatorOverload(const QString& funcName); + bool isOperatorOverload() const; + + bool isArithmeticOperator() const; + bool isBitwiseOperator() const; // Includes shift operator + bool isComparisonOperator() const; + /// Returns whether this is a comparison accepting owner class + /// (bool operator==(QByteArray,QByteArray) but not bool operator==(QByteArray,const char *) + bool isSymmetricalComparisonOperator() const; + bool isIncDecrementOperator() const; + bool isLogicalOperator() const; + bool isAssignmentOperator() const; // Assignment or move assignment + bool isGetter() const; + /// Returns whether it is a Qt-style isNull() method suitable for nb_bool + bool isQtIsNullMethod() const; + + /** + * Informs the arity of the operator or -1 if the function is not + * an operator overload. + * /return the arity of the operator or -1 + */ + int arityOfOperator() const; + bool isUnaryOperator() const { return arityOfOperator() == 1; } + bool isBinaryOperator() const { return arityOfOperator() == 2; } + bool isInplaceOperator() const; + + bool isVirtual() const; + bool allowThread() const; + QString modifiedName() const; + + QString minimalSignature() const; + /// List of signatures matched for modifications + QStringList modificationSignatures() const; + // Signature with replaced argument types and return type for overload + // decisor comment. + QString signatureComment() const; + QString debugSignature() const; // including virtual/override/final, etc., for debugging only. + + bool isModifiedRemoved(AbstractMetaClassCPtr cls = {}) const; + bool isModifiedFinal(AbstractMetaClassCPtr cls = {}) const; + + bool isVoid() const; + + const AbstractMetaType &type() const; + void setType(const AbstractMetaType &type); + + // The class that has this function as a member. + AbstractMetaClassCPtr ownerClass() const; + void setOwnerClass(const AbstractMetaClassCPtr &cls); + + // Owner excluding invisible namespaces + AbstractMetaClassCPtr targetLangOwner() const; + + // The first class in a hierarchy that declares the function + AbstractMetaClassCPtr declaringClass() const; + void setDeclaringClass(const AbstractMetaClassCPtr &cls); + + // The class that actually implements this function + AbstractMetaClassCPtr implementingClass() const; + void setImplementingClass(const AbstractMetaClassCPtr &cls); + + const AbstractMetaArgumentList &arguments() const; + AbstractMetaArgumentList &arguments(); + void setArguments(const AbstractMetaArgumentList &arguments); + void addArgument(const AbstractMetaArgument &argument); + int actualMinimumArgumentCount() const; + // Return the argument index accounting for the isModifiedRemoved arguments [0..n-1] + int actualArgumentIndex(int index) const; + + bool isDeprecated() const; + bool isDestructor() const { return functionType() == DestructorFunction; } + bool isConstructor() const; + bool isCopyConstructor() const { return functionType() == CopyConstructorFunction; } + bool isDefaultConstructor() const; + bool needsReturnType() const; + bool isInGlobalScope() const; + bool isSignal() const { return functionType() == SignalFunction; } + bool isSlot() const { return functionType() == SlotFunction; } + bool isEmptyFunction() const { return functionType() == EmptyFunction; } + bool maybeAccessor() const; + FunctionType functionType() const; + void setFunctionType(FunctionType type); + + std::optional<ComparisonOperatorType> comparisonOperatorType() const; + + bool usesRValueReferences() const; + bool generateBinding() const; + // Returns whether the function is contained in the positive list of the + // type entry if one is specified. + bool isWhiteListed() const; + + QString signature() const; + /// Return a signature qualified by class name, for error reporting. + QString classQualifiedSignature() const; + + /// Signature with unresolved typedefs as seen by the code parser + QString unresolvedSignature() const; + void setUnresolvedSignature(const QString &); + + bool isConstant() const; + void setConstant(bool constant); + + /// Returns true if the AbstractMetaFunction was added by the user via the type system description. + bool isUserAdded() const; + bool isUserAddedPythonOverride() const; + /// Returns true if the AbstractMetaFunction was declared by the user via + /// the type system description. + bool isUserDeclared() const; + + CompareResult compareTo(const AbstractMetaFunction *other) const; + bool isConstOverloadOf(const AbstractMetaFunction *other) const; + + bool operator <(const AbstractMetaFunction &a) const; + + AbstractMetaFunction *copy() const; + + QString conversionRule(TypeSystem::Language language, int idx) const; + bool hasConversionRule(TypeSystem::Language language, int idx) const; + QList<ReferenceCount> + referenceCounts(const AbstractMetaClassCPtr &cls, int idx = -2) const; + ArgumentOwner argumentOwner(const AbstractMetaClassCPtr &cls, int idx) const; + + // Returns the ownership rules for the given argument (target lang). + TypeSystem::Ownership + argumentTargetOwnership(const AbstractMetaClassCPtr &cls, int idx) const; + + const QString &modifiedTypeName() const; + bool isTypeModified() const { return !modifiedTypeName().isEmpty(); } + bool generateOpaqueContainerReturn() const; + + bool isModifiedToArray(int argumentIndex) const; + + void applyTypeModifications(); + + /// Return the (modified) type for the signature; modified-pyi-type, modified-type + QString pyiTypeReplaced(int argumentIndex) const; + + bool argumentRemoved(int) const; + /** + * Verifies if any modification to the function is an inject code. + * \return true if there is inject code modifications to the function. + */ + bool hasInjectedCode() const; + /** + * Returns a list of code snips for this function. + * The code snips can be filtered by position and language. + * \return list of code snips + */ + CodeSnipList injectedCodeSnips(TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny, + TypeSystem::Language language = TypeSystem::All) const; + bool injectedCodeContains(const QRegularExpression &pattern, + TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny, + TypeSystem::Language language = TypeSystem::All) const; + bool injectedCodeContains(QStringView pattern, + TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny, + TypeSystem::Language language = TypeSystem::All) const; + + /** + * Verifies if any modification to the function alters/removes its + * arguments types or default values. + * \return true if there is some modification to function signature + */ + bool hasSignatureModifications() const; + + const FunctionModificationList &modifications(AbstractMetaClassCPtr implementor = {}) const; + void clearModificationsCache(); + + const DocModificationList addedFunctionDocModifications() const; + + static FunctionModificationList findClassModifications(const AbstractMetaFunction *f, + AbstractMetaClassCPtr implementor); + static FunctionModificationList findGlobalModifications(const AbstractMetaFunction *f); + + /** + * Return the argument name if there is a modification the renamed value will be returned + */ + QString argumentName(int index, bool create = true, AbstractMetaClassCPtr cl = {}) const; + + int propertySpecIndex() const; + void setPropertySpecIndex(int i); + + FunctionTypeEntryPtr typeEntry() const; + void setTypeEntry(const FunctionTypeEntryPtr &typeEntry); + + QString targetLangPackage() const; + + bool isCallOperator() const; + + static AbstractMetaFunctionCPtr + find(const AbstractMetaFunctionCList &haystack, QAnyStringView needle); + + bool matches(OperatorQueryOptions) const; + + // for the meta builder only + void setAllowThreadModification(TypeSystem::AllowThread am); + void setExceptionHandlingModification(TypeSystem::ExceptionHandling em); + + int overloadNumber() const; + + TypeSystem::SnakeCase snakeCase() const; + + // Query functions for generators + /// Verifies if any of the function's code injections of the "native" + /// type needs the type system variable "%PYSELF". + /// \return true if the function's native code snippets use "%PYSELF" + bool injectedCodeUsesPySelf() const; + + /// Verifies if any of the function's code injections of the "native" class makes a + /// call to the C++ method. This is used by the generator to avoid writing calls to + /// Python overrides of C++ virtual methods when the user custom code already does this. + /// \param func the function to check + /// \return true if the function's code snippets call the Python override for a C++ virtual method + bool injectedCodeCallsPythonOverride() const; + + /// Verifies if any of the function's code injections attributes values to + /// the return variable (%0 or %PYARG_0). + /// \param language the kind of code snip + /// \return true if the function's code attributes values to "%0" or "%PYARG_0" + bool injectedCodeHasReturnValueAttribution(TypeSystem::Language language = + TypeSystem::TargetLangCode) const; + + /// Verifies if any of the function's code injections uses the type system variable + /// for function arguments of a given index. + bool injectedCodeUsesArgument(int argumentIndex) const; + + bool isVisibilityModifiedToPrivate() const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebugBrief(QDebug &debug) const; + void formatDebugVerbose(QDebug &debug) const; +#endif + + SourceLocation sourceLocation() const; + void setSourceLocation(const SourceLocation &sourceLocation); + + static const char *pythonRichCompareOpCode(ComparisonOperatorType ct); + static const char *cppComparisonOperator(ComparisonOperatorType ct); + +private: + template <class Predicate> + bool traverseCodeSnips(Predicate predicate, + TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny, + TypeSystem::Language language = TypeSystem::All) const; + bool autoDetectAllowThread() const; + + QScopedPointer<AbstractMetaFunctionPrivate> d; +}; + +inline bool AbstractMetaFunction::isAbstract() const +{ + return cppAttributes().testFlag(FunctionAttribute::Abstract); +} + +inline bool AbstractMetaFunction::isStatic() const +{ + return cppAttributes().testFlag(FunctionAttribute::Static); +} + +inline bool AbstractMetaFunction::isClassMethod() const +{ + return attributes().testFlag(ClassMethod); +} + +inline bool AbstractMetaFunction::isPropertyReader() const +{ + return attributes().testFlag(PropertyReader); +} + +inline bool AbstractMetaFunction::isPropertyWriter() const +{ + return attributes().testFlag(PropertyWriter); +} + +inline bool AbstractMetaFunction::isPropertyResetter() const +{ + return attributes().testFlag(PropertyResetter); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractMetaFunction::CompareResult) + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractMetaFunction::Attributes); + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractMetaFunction::Flags); + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const AbstractMetaFunction *af); +#endif + +#endif // ABSTRACTMETAFUNCTION_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.cpp b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp new file mode 100644 index 000000000..fb49cc9d0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.cpp @@ -0,0 +1,1983 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetalang.h" +#include "anystringview_helpers.h" +#include "abstractmetalang_helpers.h" +#include "abstractmetaargument.h" +#include "abstractmetaenum.h" +#include "abstractmetafunction.h" +#include "abstractmetatype.h" +#include "abstractmetafield.h" +#include "parser/codemodel.h" +#include "documentation.h" +#include "messages.h" +#include "modifications.h" +#include "propertyspec.h" +#include "reporthandler.h" +#include "sourcelocation.h" +#include "typedatabase.h" +#include "enumtypeentry.h" +#include "namespacetypeentry.h" +#include "usingmember.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> + +#include <algorithm> + +using namespace Qt::StringLiterals; + +bool function_sorter(const AbstractMetaFunctionCPtr &a, const AbstractMetaFunctionCPtr &b) +{ + return a->signature() < b->signature(); +} + +class AbstractMetaClassPrivate +{ +public: + AbstractMetaClassPrivate() + : m_hasVirtuals(false), + m_isPolymorphic(false), + m_hasNonpublic(false), + m_hasNonPrivateConstructor(false), + m_hasPrivateConstructor(false), + m_hasDeletedDefaultConstructor(false), + m_hasDeletedCopyConstructor(false), + m_functionsFixed(false), + m_inheritanceDone(false), + m_hasPrivateDestructor(false), + m_hasProtectedDestructor(false), + m_hasVirtualDestructor(false), + m_isTypeDef(false), + m_hasToStringCapability(false), + m_valueTypeWithCopyConstructorOnly(false), + m_hasCachedWrapper(false) + { + } + + void addFunction(const AbstractMetaFunctionCPtr &function); + static AbstractMetaFunction * + createFunction(const QString &name, AbstractMetaFunction::FunctionType t, + Access access, const AbstractMetaArgumentList &arguments, + const AbstractMetaType &returnType, const AbstractMetaClassPtr &q); + void addConstructor(AbstractMetaFunction::FunctionType t, + Access access, + const AbstractMetaArgumentList &arguments, + const AbstractMetaClassPtr &q); + void addUsingConstructors(const AbstractMetaClassPtr &q); + void sortFunctions(); + void setFunctions(const AbstractMetaFunctionCList &functions, + const AbstractMetaClassCPtr &q); + bool isUsingMember(const AbstractMetaClassCPtr &c, const QString &memberName, + Access minimumAccess) const; + bool hasConstructors() const; + qsizetype indexOfProperty(const QString &name) const; + + uint m_hasVirtuals : 1; + uint m_isPolymorphic : 1; + uint m_hasNonpublic : 1; + uint m_hasNonPrivateConstructor : 1; + uint m_hasPrivateConstructor : 1; + uint m_hasDeletedDefaultConstructor : 1; + uint m_hasDeletedCopyConstructor : 1; + uint m_functionsFixed : 1; + uint m_inheritanceDone : 1; // m_baseClasses has been populated from m_baseClassNames + uint m_hasPrivateDestructor : 1; + uint m_hasProtectedDestructor : 1; + uint m_hasVirtualDestructor : 1; + uint m_isTypeDef : 1; + uint m_hasToStringCapability : 1; + uint m_valueTypeWithCopyConstructorOnly : 1; + mutable uint m_hasCachedWrapper : 1; + + Documentation m_doc; + + AbstractMetaClassCPtr m_enclosingClass; + AbstractMetaClassCPtr m_defaultSuperclass; + AbstractMetaClassCList m_baseClasses; // Real base classes after setting up inheritance + AbstractMetaTypeList m_baseTemplateInstantiations; + AbstractMetaClassCPtr m_extendedNamespace; + + AbstractMetaClassCPtr m_templateBaseClass; + AbstractMetaFunctionCList m_functions; + AbstractMetaFunctionCList m_userAddedPythonOverrides; + AbstractMetaFieldList m_fields; + AbstractMetaEnumList m_enums; + QList<QPropertySpec> m_propertySpecs; + AbstractMetaClassCList m_innerClasses; + QString m_hashFunction; + + AbstractMetaFunctionCList m_externalConversionOperators; + + QStringList m_baseClassNames; // Base class names from C++, including rejected + TypeEntryCList m_templateArgs; + ComplexTypeEntryPtr m_typeEntry; + SourceLocation m_sourceLocation; + UsingMembers m_usingMembers; + + mutable AbstractMetaClass::CppWrapper m_cachedWrapper; + AbstractMetaClass::Attributes m_attributes; + + bool m_stream = false; + uint m_toStringCapabilityIndirections = 0; +}; + +AbstractMetaClass::AbstractMetaClass() : d(new AbstractMetaClassPrivate) +{ +} + +AbstractMetaClass::~AbstractMetaClass() = default; + +AbstractMetaClass::Attributes AbstractMetaClass::attributes() const +{ + return d->m_attributes; +} + +void AbstractMetaClass::setAttributes(Attributes attributes) +{ + d->m_attributes = attributes; +} + +void AbstractMetaClass::operator+=(AbstractMetaClass::Attribute attribute) +{ + d->m_attributes.setFlag(attribute); +} + +void AbstractMetaClass::operator-=(AbstractMetaClass::Attribute attribute) +{ + d->m_attributes.setFlag(attribute, false); +} + +bool AbstractMetaClass::isPolymorphic() const +{ + return d->m_isPolymorphic; +} + +/******************************************************************************* + * Returns a list of all the functions with a given name + */ +AbstractMetaFunctionCList AbstractMetaClass::queryFunctionsByName(const QString &name) const +{ + AbstractMetaFunctionCList returned; + for (const auto &function : d->m_functions) { + if (function->name() == name) + returned.append(function); + } + + return returned; +} + +/******************************************************************************* + * Returns a list of all the functions retrieved during parsing which should + * be added to the API. + */ +AbstractMetaFunctionCList AbstractMetaClass::functionsInTargetLang() const +{ + FunctionQueryOptions default_flags = FunctionQueryOption::NormalFunctions + | FunctionQueryOption::Visible | FunctionQueryOption::NotRemoved; + + // Constructors + AbstractMetaFunctionCList returned = queryFunctions(FunctionQueryOption::AnyConstructor + | default_flags); + + returned += queryFunctions(FunctionQueryOption::NonStaticFunctions + | default_flags); + + // Static functions + returned += queryFunctions(FunctionQueryOption::StaticFunctions + | default_flags); + + // Empty, private functions, since they aren't caught by the other ones + returned += queryFunctions(FunctionQueryOption::Empty | FunctionQueryOption::Invisible); + + return returned; +} + +AbstractMetaFunctionCList AbstractMetaClass::implicitConversions() const +{ + if (!isCopyConstructible() && !hasExternalConversionOperators()) + return {}; + + AbstractMetaFunctionCList returned; + const auto list = queryFunctions(FunctionQueryOption::Constructors) + externalConversionOperators(); + + // Exclude anything that uses rvalue references, be it a move + // constructor "QPolygon(QPolygon &&)" or something else like + // "QPolygon(QVector<QPoint> &&)". + for (const auto &f : list) { + if ((f->actualMinimumArgumentCount() == 1 || f->arguments().size() == 1 || f->isConversionOperator()) + && !f->isExplicit() + && !f->usesRValueReferences() + && !f->isModifiedRemoved() + && f->wasPublic()) { + returned += f; + } + } + return returned; +} + +AbstractMetaFunctionCList AbstractMetaClass::operatorOverloads(OperatorQueryOptions query) const +{ + const auto &list = queryFunctions(FunctionQueryOption::OperatorOverloads + | FunctionQueryOption::Visible); + AbstractMetaFunctionCList returned; + for (const auto &f : list) { + if (f->matches(query)) + returned += f; + } + + return returned; +} + +bool AbstractMetaClass::hasArithmeticOperatorOverload() const +{ + for (const auto & f: d->m_functions) { + if (f->ownerClass() == f->implementingClass() && f->isArithmeticOperator() && !f->isPrivate()) + return true; + } + return false; +} + +bool AbstractMetaClass::hasIncDecrementOperatorOverload() const +{ + for (const auto & f: d->m_functions) { + if (f->ownerClass() == f->implementingClass() + && f->isIncDecrementOperator() && !f->isPrivate()) { + return true; + } + } + return false; +} + +bool AbstractMetaClass::hasBitwiseOperatorOverload() const +{ + for (const auto & f: d->m_functions) { + if (f->ownerClass() == f->implementingClass() && f->isBitwiseOperator() && !f->isPrivate()) + return true; + } + return false; +} + +bool AbstractMetaClass::hasComparisonOperatorOverload() const +{ + for (const auto &f : d->m_functions) { + if (f->ownerClass() == f->implementingClass() && f->isComparisonOperator() && !f->isPrivate()) + return true; + } + return false; +} + +bool AbstractMetaClass::hasLogicalOperatorOverload() const +{ + for (const auto &f : d->m_functions) { + if (f->ownerClass() == f->implementingClass() && f->isLogicalOperator() && !f->isPrivate()) + return true; + } + return false; +} + +const AbstractMetaFieldList &AbstractMetaClass::fields() const +{ + return d->m_fields; +} + +AbstractMetaFieldList &AbstractMetaClass::fields() +{ + return d->m_fields; +} + +void AbstractMetaClass::setFields(const AbstractMetaFieldList &fields) +{ + d->m_fields = fields; +} + +void AbstractMetaClass::addField(const AbstractMetaField &field) +{ + d->m_fields << field; +} + +bool AbstractMetaClass::hasStaticFields() const +{ + return std::any_of(d->m_fields.cbegin(), d->m_fields.cend(), + [](const AbstractMetaField &f) { return f.isStatic(); }); +} + +void AbstractMetaClass::sortFunctions() +{ + d->sortFunctions(); +} + +AbstractMetaClassCPtr AbstractMetaClass::templateBaseClass() const +{ + return d->m_templateBaseClass; +} + +void AbstractMetaClass::setTemplateBaseClass(const AbstractMetaClassCPtr &cls) +{ + d->m_templateBaseClass = cls; +} + +const AbstractMetaFunctionCList &AbstractMetaClass::functions() const +{ + return d->m_functions; +} + +const AbstractMetaFunctionCList &AbstractMetaClass::userAddedPythonOverrides() const +{ + return d->m_userAddedPythonOverrides; +} + +void AbstractMetaClassPrivate::sortFunctions() +{ + std::sort(m_functions.begin(), m_functions.end(), function_sorter); +} + +void AbstractMetaClassPrivate::setFunctions(const AbstractMetaFunctionCList &functions, + const AbstractMetaClassCPtr &q) +{ + m_functions = functions; + + // Functions must be sorted by name before next loop + sortFunctions(); + + for (const auto &f : std::as_const(m_functions)) { + std::const_pointer_cast<AbstractMetaFunction>(f)->setOwnerClass(q); + if (!f->isPublic()) + m_hasNonpublic = true; + } +} + +const QList<QPropertySpec> &AbstractMetaClass::propertySpecs() const +{ + return d->m_propertySpecs; +} + +void AbstractMetaClass::addPropertySpec(const QPropertySpec &spec) +{ + d->m_propertySpecs << spec; +} + +void AbstractMetaClass::setPropertyDocumentation(const QString &name, const Documentation &doc) +{ + const auto index = d->indexOfProperty(name); + if (index >= 0) + d->m_propertySpecs[index].setDocumentation(doc); +} + +void AbstractMetaClassPrivate::addFunction(const AbstractMetaFunctionCPtr &function) +{ + Q_ASSERT(!function->signature().startsWith(u'(')); + + if (!function->isDestructor()) + m_functions << function; + else + Q_ASSERT(false); //memory leak + + m_hasVirtuals |= function->isVirtual(); + m_isPolymorphic |= m_hasVirtuals; + m_hasNonpublic |= !function->isPublic(); + m_hasNonPrivateConstructor |= !function->isPrivate() + && function->functionType() == AbstractMetaFunction::ConstructorFunction; +} + +void AbstractMetaClass::addFunction(const AbstractMetaClassPtr &klass, + const AbstractMetaFunctionCPtr &function) +{ + auto nonConstF = std::const_pointer_cast<AbstractMetaFunction>(function); + nonConstF->setOwnerClass(klass); + + // Set the default value of the declaring class. This may be changed + // in fixFunctions later on + nonConstF->setDeclaringClass(klass); + + // Some of the queries below depend on the implementing class being set + // to function properly. Such as function modifications + nonConstF->setImplementingClass(klass); + + if (function->isUserAddedPythonOverride()) { + nonConstF->setConstant(false); + nonConstF->setCppAttribute(FunctionAttribute::Static); + klass->d->m_userAddedPythonOverrides.append(function); + } else { + klass->d->addFunction(function); + } +} + +bool AbstractMetaClass::hasSignal(const AbstractMetaFunction *other) const +{ + if (!other->isSignal()) + return false; + + for (const auto &f : d->m_functions) { + if (f->isSignal() && f->compareTo(other) & AbstractMetaFunction::EqualName) + return other->modifiedName() == f->modifiedName(); + } + + return false; +} + + +QString AbstractMetaClass::name() const +{ + return d->m_typeEntry->targetLangEntryName(); +} + +const Documentation &AbstractMetaClass::documentation() const +{ + return d->m_doc; +} + +void AbstractMetaClass::setDocumentation(const Documentation &doc) +{ + d->m_doc = doc; +} + +QString AbstractMetaClass::baseClassName() const +{ + return d->m_baseClasses.isEmpty() ? QString() : d->m_baseClasses.constFirst()->name(); +} + +// Attribute "default-superclass" +AbstractMetaClassCPtr AbstractMetaClass::defaultSuperclass() const +{ + return d->m_defaultSuperclass; +} + +void AbstractMetaClass::setDefaultSuperclass(const AbstractMetaClassPtr &s) +{ + d->m_defaultSuperclass = s; +} + +AbstractMetaClassCPtr AbstractMetaClass::baseClass() const +{ + return d->m_baseClasses.value(0, nullptr); +} + +const AbstractMetaClassCList &AbstractMetaClass::baseClasses() const +{ + Q_ASSERT(inheritanceDone() || !needsInheritanceSetup()); + return d->m_baseClasses; +} + +// base classes including "defaultSuperclass". +AbstractMetaClassCList AbstractMetaClass::typeSystemBaseClasses() const +{ + AbstractMetaClassCList result = d->m_baseClasses; + if (d->m_defaultSuperclass) { + result.removeAll(d->m_defaultSuperclass); + result.prepend(d->m_defaultSuperclass); + } + return result; +} + +// Recursive list of all base classes including defaultSuperclass +AbstractMetaClassCList AbstractMetaClass::allTypeSystemAncestors() const +{ + AbstractMetaClassCList result; + const auto baseClasses = typeSystemBaseClasses(); + for (const auto &base : baseClasses) { + result.append(base); + result.append(base->allTypeSystemAncestors()); + } + return result; +} + +void AbstractMetaClass::addBaseClass(const AbstractMetaClassCPtr &baseClass) +{ + Q_ASSERT(baseClass); + d->m_baseClasses.append(baseClass); + d->m_isPolymorphic |= baseClass->isPolymorphic(); +} + +void AbstractMetaClass::setBaseClass(const AbstractMetaClassCPtr &baseClass) +{ + if (baseClass) { + d->m_baseClasses.prepend(baseClass); + d->m_isPolymorphic |= baseClass->isPolymorphic(); + } +} + +AbstractMetaClassCPtr AbstractMetaClass::extendedNamespace() const +{ + return d->m_extendedNamespace; +} + +void AbstractMetaClass::setExtendedNamespace(const AbstractMetaClassCPtr &e) +{ + d->m_extendedNamespace = e; +} + +const AbstractMetaClassCList &AbstractMetaClass::innerClasses() const +{ + return d->m_innerClasses; +} + +void AbstractMetaClass::addInnerClass(const AbstractMetaClassPtr &cl) +{ + d->m_innerClasses << cl; +} + +void AbstractMetaClass::setInnerClasses(const AbstractMetaClassCList &innerClasses) +{ + d->m_innerClasses = innerClasses; +} + +QString AbstractMetaClass::package() const +{ + return d->m_typeEntry->targetLangPackage(); +} + +bool AbstractMetaClass::isNamespace() const +{ + return d->m_typeEntry->isNamespace(); +} + +// Is an invisible namespaces whose functions/enums +// should be mapped to the global space. +bool AbstractMetaClass::isInvisibleNamespace() const +{ + return d->m_typeEntry->isNamespace() && d->m_typeEntry->generateCode() + && !NamespaceTypeEntry::isVisibleScope(d->m_typeEntry); +} + +bool AbstractMetaClass::isInlineNamespace() const +{ + bool result = false; + if (d->m_typeEntry->isNamespace()) { + const auto nte = std::static_pointer_cast<const NamespaceTypeEntry>(d->m_typeEntry); + result = nte->isInlineNamespace(); + } + return result; +} + +bool AbstractMetaClass::isQtNamespace() const +{ + return isNamespace() && name() == u"Qt"; +} + +QString AbstractMetaClass::qualifiedCppName() const +{ + return d->m_typeEntry->qualifiedCppName(); +} + +bool AbstractMetaClass::hasFunction(const QString &str) const +{ + return bool(findFunction(str)); +} + +AbstractMetaFunctionCPtr AbstractMetaClass::findFunction(QAnyStringView functionName) const +{ + return AbstractMetaFunction::find(d->m_functions, functionName); +} + +AbstractMetaFunctionCList AbstractMetaClass::findFunctions(QAnyStringView functionName) const +{ + AbstractMetaFunctionCList result; + std::copy_if(d->m_functions.cbegin(), d->m_functions.cend(), + std::back_inserter(result), + [&functionName](const AbstractMetaFunctionCPtr &f) { + return f->name() == functionName; + }); + return result; +} + +AbstractMetaFunctionCPtr AbstractMetaClass::findOperatorBool() const +{ + auto it = std::find_if(d->m_functions.cbegin(), d->m_functions.cend(), + [](const AbstractMetaFunctionCPtr &f) { + return f->isOperatorBool(); + }); + if (it == d->m_functions.cend()) + return {}; + return *it; +} + +AbstractMetaFunctionCPtr AbstractMetaClass::findQtIsNullMethod() const +{ + auto it = std::find_if(d->m_functions.cbegin(), d->m_functions.cend(), + [](const AbstractMetaFunctionCPtr &f) { + return f->isQtIsNullMethod(); + }); + if (it == d->m_functions.cend()) + return {}; + return *it; +} + +bool AbstractMetaClass::hasProtectedFields() const +{ + for (const AbstractMetaField &field : d->m_fields) { + if (field.isProtected()) + return true; + } + return false; +} + +const TypeEntryCList &AbstractMetaClass::templateArguments() const +{ + return d->m_templateArgs; +} + +void AbstractMetaClass::setTemplateArguments(const TypeEntryCList &args) +{ + d->m_templateArgs = args; +} + +const QStringList &AbstractMetaClass::baseClassNames() const +{ + return d->m_baseClassNames; +} + +void AbstractMetaClass::setBaseClassNames(const QStringList &names) +{ + d->m_baseClassNames = names; +} + +ComplexTypeEntryCPtr AbstractMetaClass::typeEntry() const +{ + return d->m_typeEntry; +} + +ComplexTypeEntryPtr AbstractMetaClass::typeEntry() +{ + return d->m_typeEntry; +} + +void AbstractMetaClass::setTypeEntry(const ComplexTypeEntryPtr &type) +{ + d->m_typeEntry = type; +} + +QString AbstractMetaClass::hashFunction() const +{ + return d->m_hashFunction; +} + +void AbstractMetaClass::setHashFunction(const QString &f) +{ + d->m_hashFunction = f; +} + +bool AbstractMetaClass::hasHashFunction() const +{ + return !d->m_hashFunction.isEmpty(); +} + +// Search whether a functions is a property setter/getter/reset +AbstractMetaClass::PropertyFunctionSearchResult + AbstractMetaClass::searchPropertyFunction(const QString &name) const +{ + for (qsizetype i = 0, size = d->m_propertySpecs.size(); i < size; ++i) { + const auto &propertySpec = d->m_propertySpecs.at(i); + if (name == propertySpec.read()) + return PropertyFunctionSearchResult{i, PropertyFunction::Read}; + if (name == propertySpec.write()) + return PropertyFunctionSearchResult{i, PropertyFunction::Write}; + if (name == propertySpec.reset()) + return PropertyFunctionSearchResult{i, PropertyFunction::Reset}; + if (name == propertySpec.notify()) + return PropertyFunctionSearchResult{i, PropertyFunction::Notify}; + } + return PropertyFunctionSearchResult{-1, PropertyFunction::Read}; +} + +std::optional<QPropertySpec> + AbstractMetaClass::propertySpecByName(const QString &name) const +{ + const auto index = d->indexOfProperty(name); + if (index >= 0) + return d->m_propertySpecs.at(index); + return {}; +} + +const AbstractMetaFunctionCList &AbstractMetaClass::externalConversionOperators() const +{ + return d->m_externalConversionOperators; +} + +void AbstractMetaClass::addExternalConversionOperator(const AbstractMetaFunctionCPtr &conversionOp) +{ + if (!d->m_externalConversionOperators.contains(conversionOp)) + d->m_externalConversionOperators.append(conversionOp); +} + +bool AbstractMetaClass::hasExternalConversionOperators() const +{ + return !d->m_externalConversionOperators.isEmpty(); +} + +bool AbstractMetaClass::hasTemplateBaseClassInstantiations() const +{ + return d->m_templateBaseClass != nullptr && !d->m_baseTemplateInstantiations.isEmpty(); +} + +const AbstractMetaTypeList &AbstractMetaClass::templateBaseClassInstantiations() const +{ + return d->m_baseTemplateInstantiations; +} + +void AbstractMetaClass::setTemplateBaseClassInstantiations(const AbstractMetaTypeList &instantiations) +{ + Q_ASSERT(d->m_templateBaseClass != nullptr); + d->m_baseTemplateInstantiations = instantiations; +} + +void AbstractMetaClass::setTypeDef(bool typeDef) +{ + d->m_isTypeDef = typeDef; +} + +bool AbstractMetaClass::isTypeDef() const +{ + return d->m_isTypeDef; +} + +bool AbstractMetaClass::isStream() const +{ + return d->m_stream; +} + +void AbstractMetaClass::setStream(bool stream) +{ + d->m_stream = stream; +} + +bool AbstractMetaClass::hasToStringCapability() const +{ + return d->m_hasToStringCapability; +} + +void AbstractMetaClass::setToStringCapability(bool value, uint indirections) +{ + d->m_hasToStringCapability = value; + d->m_toStringCapabilityIndirections = indirections; +} + +uint AbstractMetaClass::toStringCapabilityIndirections() const +{ + return d->m_toStringCapabilityIndirections; +} + +// Does any of the base classes require deletion in the main thread? +bool AbstractMetaClass::deleteInMainThread() const +{ + return typeEntry()->deleteInMainThread() + || (!d->m_baseClasses.isEmpty() && d->m_baseClasses.constFirst()->deleteInMainThread()); +} + +bool AbstractMetaClassPrivate::hasConstructors() const +{ + return AbstractMetaClass::queryFirstFunction(m_functions, + FunctionQueryOption::AnyConstructor) != nullptr; +} + +qsizetype AbstractMetaClassPrivate::indexOfProperty(const QString &name) const +{ + for (qsizetype i = 0; i < m_propertySpecs.size(); ++i) { + if (m_propertySpecs.at(i).name() == name) + return i; + } + return -1; +} + +bool AbstractMetaClass::hasConstructors() const +{ + return d->hasConstructors(); +} + +AbstractMetaFunctionCPtr AbstractMetaClass::copyConstructor() const +{ + for (const auto &f : d->m_functions) { + if (f->functionType() == AbstractMetaFunction::CopyConstructorFunction) + return f; + } + return {}; +} + +bool AbstractMetaClass::hasCopyConstructor() const +{ + return copyConstructor() != nullptr; +} + +bool AbstractMetaClass::hasPrivateCopyConstructor() const +{ + const auto copyCt = copyConstructor(); + return copyCt && copyCt->isPrivate(); +} + +void AbstractMetaClassPrivate::addConstructor(AbstractMetaFunction::FunctionType t, + Access access, + const AbstractMetaArgumentList &arguments, + const AbstractMetaClassPtr &q) +{ + auto *f = createFunction(q->name(), t, access, arguments, AbstractMetaType::createVoid(), q); + if (access != Access::Private) + m_hasNonPrivateConstructor = true; + f->setAttributes(AbstractMetaFunction::AddedMethod); + addFunction(AbstractMetaFunctionCPtr(f)); +} + +void AbstractMetaClass::addDefaultConstructor(const AbstractMetaClassPtr &klass) +{ + klass->d->addConstructor(AbstractMetaFunction::ConstructorFunction, + Access::Public, {}, klass); +} + +void AbstractMetaClass::addDefaultCopyConstructor(const AbstractMetaClassPtr &klass) +{ + AbstractMetaType argType(klass->typeEntry()); + argType.setReferenceType(LValueReference); + argType.setConstant(true); + argType.setTypeUsagePattern(AbstractMetaType::ValuePattern); + + AbstractMetaArgument arg; + arg.setType(argType); + arg.setName(klass->name()); + + klass->d->addConstructor(AbstractMetaFunction::CopyConstructorFunction, + Access::Public, {arg}, klass); +} + +AbstractMetaFunction * + AbstractMetaClassPrivate::createFunction(const QString &name, + AbstractMetaFunction::FunctionType t, + Access access, + const AbstractMetaArgumentList &arguments, + const AbstractMetaType &returnType, + const AbstractMetaClassPtr &q) +{ + auto *f = new AbstractMetaFunction(name); + f->setType(returnType); + f->setOwnerClass(q); + f->setFunctionType(t); + f->setArguments(arguments); + f->setDeclaringClass(q); + f->setAccess(access); + f->setImplementingClass(q); + return f; +} + +static AbstractMetaType boolType() +{ + auto boolType = TypeDatabase::instance()->findType(u"bool"_s); + Q_ASSERT(boolType); + AbstractMetaType result(boolType); + result.decideUsagePattern(); + return result; +} + +// Helper to synthesize comparison operators from a spaceship operator. Since +// shiboken also generates code for comparing to different types, this fits +// better than of handling it in the generator code. +void AbstractMetaClass::addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c) +{ + static const auto returnType = boolType(); + + AbstractMetaType selfType(c->typeEntry()); + selfType.setConstant(true); + selfType.setReferenceType(LValueReference); + selfType.decideUsagePattern(); + AbstractMetaArgument selfArgument; + selfArgument.setType(selfType); + selfArgument.setName(u"rhs"_s); + AbstractMetaArgumentList arguments(1, selfArgument); + + static const char *operators[] + = {"operator==", "operator!=", "operator<", "operator<=", "operator>", "operator>="}; + for (auto *op : operators) { + auto *f = AbstractMetaClassPrivate::createFunction(QLatin1StringView(op), + AbstractMetaFunction::ComparisonOperator, + Access::Public, arguments, + returnType, c); + c->d->addFunction(AbstractMetaFunctionCPtr(f)); + } +} + +bool AbstractMetaClass::hasNonPrivateConstructor() const +{ + return d->m_hasNonPrivateConstructor; +} + +void AbstractMetaClass::setHasNonPrivateConstructor(bool value) +{ + d->m_hasNonPrivateConstructor = value; +} + +bool AbstractMetaClass::hasPrivateConstructor() const +{ + return d->m_hasPrivateConstructor; +} + +void AbstractMetaClass::setHasPrivateConstructor(bool value) +{ + d->m_hasPrivateConstructor = value; +} + +bool AbstractMetaClass::hasDeletedDefaultConstructor() const +{ + return d->m_hasDeletedDefaultConstructor; +} + +void AbstractMetaClass::setHasDeletedDefaultConstructor(bool value) +{ + d->m_hasDeletedDefaultConstructor = value; +} + +bool AbstractMetaClass::hasDeletedCopyConstructor() const +{ + return d->m_hasDeletedCopyConstructor; +} + +void AbstractMetaClass::setHasDeletedCopyConstructor(bool value) +{ + d->m_hasDeletedCopyConstructor = value; +} + +bool AbstractMetaClass::hasPrivateDestructor() const +{ + return d->m_hasPrivateDestructor; +} + +void AbstractMetaClass::setHasPrivateDestructor(bool value) +{ + d->m_hasPrivateDestructor = value; +} + +bool AbstractMetaClass::hasProtectedDestructor() const +{ + return d->m_hasProtectedDestructor; +} + +void AbstractMetaClass::setHasProtectedDestructor(bool value) +{ + d->m_hasProtectedDestructor = value; +} + +bool AbstractMetaClass::hasVirtualDestructor() const +{ + return d->m_hasVirtualDestructor; +} + +void AbstractMetaClass::setHasVirtualDestructor(bool value) +{ + d->m_hasVirtualDestructor = value; + if (value) + d->m_hasVirtuals = d->m_isPolymorphic = 1; +} + +bool AbstractMetaClass::isDefaultConstructible() const +{ + // Private constructors are skipped by the builder. + if (hasDeletedDefaultConstructor() || hasPrivateConstructor()) + return false; + const AbstractMetaFunctionCList ctors = + queryFunctions(FunctionQueryOption::Constructors); + for (const auto &ct : ctors) { + if (ct->isDefaultConstructor()) + return ct->isPublic(); + } + return ctors.isEmpty() && isImplicitlyDefaultConstructible(); +} + +// Non-comprehensive check for default constructible field +// (non-ref or not const value). +static bool defaultConstructibleField(const AbstractMetaField &f) +{ + if (f.isStatic()) + return true; + const auto &type = f.type(); + return type.referenceType() == NoReference + && !(type.indirections() == 0 && type.isConstant()); // no const values +} + +bool AbstractMetaClass::isImplicitlyDefaultConstructible() const +{ + return std::all_of(d->m_fields.cbegin(), d->m_fields.cend(), + defaultConstructibleField) + && std::all_of(d->m_baseClasses.cbegin(), d->m_baseClasses.cend(), + [] (const AbstractMetaClassCPtr &c) { + return c->isDefaultConstructible(); + }); +} + +static bool canAddDefaultConstructorHelper(const AbstractMetaClass *cls) +{ + return !cls->isNamespace() + && !cls->hasDeletedDefaultConstructor() + && !cls->attributes().testFlag(AbstractMetaClass::HasRejectedConstructor) + && !cls->hasPrivateDestructor(); +} + +bool AbstractMetaClass::canAddDefaultConstructor() const +{ + return canAddDefaultConstructorHelper(this) && !hasConstructors() + && !hasPrivateConstructor() && isImplicitlyDefaultConstructible(); +} + +bool AbstractMetaClass::isCopyConstructible() const +{ + // Private constructors are skipped by the builder. + if (hasDeletedCopyConstructor() || hasPrivateCopyConstructor()) + return false; + const AbstractMetaFunctionCList copyCtors = + queryFunctions(FunctionQueryOption::CopyConstructor); + return copyCtors.isEmpty() + ? isImplicitlyCopyConstructible() + : copyCtors.constFirst()->isPublic(); +} + +bool AbstractMetaClass::isImplicitlyCopyConstructible() const +{ + // Fields are currently not considered + return std::all_of(d->m_baseClasses.cbegin(), d->m_baseClasses.cend(), + [] (const AbstractMetaClassCPtr &c) { + return c->isCopyConstructible(); + }); +} + +bool AbstractMetaClass::canAddDefaultCopyConstructor() const +{ + if (!canAddDefaultConstructorHelper(this) + || !d->m_typeEntry->isValue() || isAbstract() + || hasPrivateCopyConstructor() || hasCopyConstructor()) { + return false; + } + return isImplicitlyCopyConstructible(); +} + +static bool classHasParentManagement(const AbstractMetaClassCPtr &c) +{ + const auto flags = c->typeEntry()->typeFlags(); + return flags.testFlag(ComplexTypeEntry::ParentManagement); +} + +TypeEntryCPtr parentManagementEntry(const AbstractMetaClassCPtr &klass) +{ + if (klass->typeEntry()->isObject()) { + if (auto c = recurseClassHierarchy(klass, classHasParentManagement)) + return c->typeEntry(); + } + return nullptr; +} + +bool AbstractMetaClass::generateExceptionHandling() const +{ + return queryFirstFunction(d->m_functions, FunctionQueryOption::Visible + | FunctionQueryOption::GenerateExceptionHandling) != nullptr; +} + +static bool needsProtectedWrapper(const AbstractMetaFunctionCPtr &func) +{ + return func->isProtected() + && !(func->isSignal() || func->isModifiedRemoved()) + && !func->isOperatorOverload(); +} + +static AbstractMetaClass::CppWrapper determineCppWrapper(const AbstractMetaClass *metaClass) +{ + + AbstractMetaClass::CppWrapper result; + + if (metaClass->isNamespace() + || metaClass->attributes().testFlag(AbstractMetaClass::FinalCppClass) + || metaClass->typeEntry()->typeFlags().testFlag(ComplexTypeEntry::DisableWrapper)) { + return result; + } + +#ifndef Q_CC_MSVC + // PYSIDE-504: When C++ 11 is used, then the destructor must always be + // declared. Only MSVC can handle this, the others generate a link error. + // See also HeaderGenerator::generateClass(). + if (metaClass->hasPrivateDestructor()) + return result; +#endif + + // Need checking for Python overrides? + if (metaClass->isPolymorphic()) + result |= AbstractMetaClass::CppVirtualMethodWrapper; + + // Is there anything protected that needs to be made accessible? + if (metaClass->hasProtectedFields() || metaClass->hasProtectedDestructor() + || std::any_of(metaClass->functions().cbegin(), metaClass->functions().cend(), + needsProtectedWrapper)) { + result |= AbstractMetaClass::CppProtectedHackWrapper; + } + return result; +} + +AbstractMetaClass::CppWrapper AbstractMetaClass::cppWrapper() const +{ + if (!d->m_hasCachedWrapper) { + d->m_cachedWrapper = determineCppWrapper(this); + d->m_hasCachedWrapper = true; + } + return d->m_cachedWrapper; +} + +const UsingMembers &AbstractMetaClass::usingMembers() const +{ + return d->m_usingMembers; +} + +void AbstractMetaClass::addUsingMember(const UsingMember &um) +{ + d->m_usingMembers.append(um); +} + +bool AbstractMetaClassPrivate::isUsingMember(const AbstractMetaClassCPtr &c, + const QString &memberName, + Access minimumAccess) const +{ + auto it = std::find_if(m_usingMembers.cbegin(), m_usingMembers.cend(), + [c, &memberName](const UsingMember &um) { + return um.baseClass == c && um.memberName == memberName; + }); + return it != m_usingMembers.cend() && it->access >= minimumAccess; +} + +bool AbstractMetaClass::isUsingMember(const AbstractMetaClassCPtr &c, + const QString &memberName, + Access minimumAccess) const +{ + return d->isUsingMember(c, memberName, minimumAccess); +} + +bool AbstractMetaClass::hasUsingMemberFor(const QString &memberName) const +{ + return std::any_of(d->m_usingMembers.cbegin(), d->m_usingMembers.cend(), + [&memberName](const UsingMember &um) { + return um.memberName == memberName; + }); +} + +/* Goes through the list of functions and returns a list of all + functions matching all of the criteria in \a query. + */ + +bool AbstractMetaClass::queryFunction(const AbstractMetaFunction *f, FunctionQueryOptions query) +{ + if ((query.testFlag(FunctionQueryOption::NotRemoved))) { + if (f->isModifiedRemoved()) + return false; + if (f->isVirtual() && f->isModifiedRemoved(f->declaringClass())) + return false; + } + + if (query.testFlag(FunctionQueryOption::Visible) && f->isPrivate()) + return false; + + if (query.testFlag(FunctionQueryOption::Invisible) && !f->isPrivate()) + return false; + + if (query.testFlag(FunctionQueryOption::Empty) && !f->isEmptyFunction()) + return false; + + if (query.testFlag(FunctionQueryOption::ClassImplements) && f->ownerClass() != f->implementingClass()) + return false; + + if (query.testFlag(FunctionQueryOption::VirtualInCppFunctions) && !f->isVirtual()) + return false; + + if (query.testFlag(FunctionQueryOption::Signals) && (!f->isSignal())) + return false; + + if (query.testFlag(FunctionQueryOption::AnyConstructor) + && (!f->isConstructor() || f->ownerClass() != f->implementingClass())) { + return false; + } + + if (query.testFlag(FunctionQueryOption::Constructors) + && (f->functionType() != AbstractMetaFunction::ConstructorFunction + || f->ownerClass() != f->implementingClass())) { + return false; + } + + if (query.testFlag(FunctionQueryOption::CopyConstructor) + && (!f->isCopyConstructor() || f->ownerClass() != f->implementingClass())) { + return false; + } + + // Destructors are never included in the functions of a class currently + /* + if ((query & Destructors) && (!f->isDestructor() + || f->ownerClass() != f->implementingClass()) + || f->isDestructor() && (query & Destructors) == 0) { + return false; + }*/ + + if (query.testFlag(FunctionQueryOption::StaticFunctions) && (!f->isStatic() || f->isSignal())) + return false; + + if (query.testFlag(FunctionQueryOption::NonStaticFunctions) && (f->isStatic())) + return false; + + if (query.testFlag(FunctionQueryOption::NormalFunctions) && (f->isSignal())) + return false; + + if (query.testFlag(FunctionQueryOption::OperatorOverloads) && !f->isOperatorOverload()) + return false; + + if (query.testFlag(FunctionQueryOption::GenerateExceptionHandling) && !f->generateExceptionHandling()) + return false; + + if (query.testFlag(FunctionQueryOption::GetAttroFunction) + && f->functionType() != AbstractMetaFunction::GetAttroFunction) { + return false; + } + + if (query.testFlag(FunctionQueryOption::SetAttroFunction) + && f->functionType() != AbstractMetaFunction::SetAttroFunction) { + return false; + } + + return true; +} + +AbstractMetaFunctionCList AbstractMetaClass::queryFunctionList(const AbstractMetaFunctionCList &list, + FunctionQueryOptions query) +{ + AbstractMetaFunctionCList result; + for (const auto &f : list) { + if (queryFunction(f.get(), query)) + result.append(f); + } + return result; +} + +AbstractMetaFunctionCPtr AbstractMetaClass::queryFirstFunction(const AbstractMetaFunctionCList &list, + FunctionQueryOptions query) +{ + for (const auto &f : list) { + if (queryFunction(f.get(), query)) + return f; + } + return {}; +} + +AbstractMetaFunctionCList AbstractMetaClass::queryFunctions(FunctionQueryOptions query) const +{ + return AbstractMetaClass::queryFunctionList(d->m_functions, query); +} + +bool AbstractMetaClass::hasSignals() const +{ + return queryFirstFunction(d->m_functions, + FunctionQueryOption::Signals + | FunctionQueryOption::Visible + | FunctionQueryOption::NotRemoved) != nullptr; +} + +AbstractMetaFunctionCList AbstractMetaClass::cppSignalFunctions() const +{ + return queryFunctions(FunctionQueryOption::Signals + | FunctionQueryOption::Visible + | FunctionQueryOption::NotRemoved); +} + +std::optional<AbstractMetaField> + AbstractMetaClass::findField(QStringView name) const +{ + return AbstractMetaField::find(d->m_fields, name); +} + +const AbstractMetaEnumList &AbstractMetaClass::enums() const +{ + return d->m_enums; +} + +AbstractMetaEnumList &AbstractMetaClass::enums() +{ + return d->m_enums; +} + +void AbstractMetaClass::setEnums(const AbstractMetaEnumList &enums) +{ + d->m_enums = enums; +} + +void AbstractMetaClass::addEnum(const AbstractMetaEnum &e) +{ + d->m_enums << e; +} + +std::optional<AbstractMetaEnum> + AbstractMetaClass::findEnum(const QString &enumName) const +{ + for (const auto &e : d->m_enums) { + if (e.name() == enumName) + return e; + } + return {}; +} + +/*! Recursively searches for the enum value named \a enumValueName in + this class and its superclasses and interfaces. +*/ +std::optional<AbstractMetaEnumValue> + AbstractMetaClass::findEnumValue(const QString &enumValueName) const +{ + for (const AbstractMetaEnum &e : std::as_const(d->m_enums)) { + auto v = e.findEnumValue(enumValueName); + if (v.has_value()) + return v; + } + if (baseClass()) + return baseClass()->findEnumValue(enumValueName); + + return {}; +} + +void AbstractMetaClass::getEnumsToBeGenerated(AbstractMetaEnumList *enumList) const +{ + for (const AbstractMetaEnum &metaEnum : d->m_enums) { + if (!metaEnum.isPrivate() && metaEnum.typeEntry()->generateCode()) + enumList->append(metaEnum); + } +} + +void AbstractMetaClass::getEnumsFromInvisibleNamespacesToBeGenerated(AbstractMetaEnumList *enumList) const +{ + if (isNamespace()) { + invisibleNamespaceRecursion([enumList](const AbstractMetaClassCPtr &c) { + c->getEnumsToBeGenerated(enumList); + }); + } +} + +void AbstractMetaClass::getFunctionsFromInvisibleNamespacesToBeGenerated(AbstractMetaFunctionCList *funcList) const +{ + if (isNamespace()) { + invisibleNamespaceRecursion([funcList](const AbstractMetaClassCPtr &c) { + funcList->append(c->functions()); + }); + } +} + +QString AbstractMetaClass::fullName() const +{ + return package() + u'.' + d->m_typeEntry->targetLangName(); +} + +static void addExtraIncludeForType(const AbstractMetaClassPtr &metaClass, + const AbstractMetaType &type) +{ + + Q_ASSERT(metaClass); + const auto entry = type.typeEntry(); + + if (entry && entry->include().isValid()) { + const auto class_entry = metaClass->typeEntry(); + class_entry->addArgumentInclude(entry->include()); + } + + if (type.hasInstantiations()) { + for (const AbstractMetaType &instantiation : type.instantiations()) + addExtraIncludeForType(metaClass, instantiation); + } +} + +static void addExtraIncludesForFunction(const AbstractMetaClassPtr &metaClass, + const AbstractMetaFunctionCPtr &meta_function) +{ + Q_ASSERT(metaClass); + Q_ASSERT(meta_function); + addExtraIncludeForType(metaClass, meta_function->type()); + + const AbstractMetaArgumentList &arguments = meta_function->arguments(); + for (const AbstractMetaArgument &argument : arguments) { + const auto &type = argument.type(); + addExtraIncludeForType(metaClass, type); + if (argument.modifiedType() != type) + addExtraIncludeForType(metaClass, argument.modifiedType()); + } +} + +static bool addSuperFunction(const AbstractMetaFunctionCPtr &f) +{ + switch (f->functionType()) { + case AbstractMetaFunction::ConstructorFunction: + case AbstractMetaFunction::CopyConstructorFunction: + case AbstractMetaFunction::MoveConstructorFunction: + case AbstractMetaFunction::AssignmentOperatorFunction: + case AbstractMetaFunction::MoveAssignmentOperatorFunction: + case AbstractMetaFunction::DestructorFunction: + return false; + default: + break; + } + return true; +} + +// Add constructors imported via "using" from the base classes. This is not +// needed for normal hidden inherited member functions since we generate a +// cast to the base class to call them into binding code. +void AbstractMetaClassPrivate::addUsingConstructors(const AbstractMetaClassPtr &q) +{ + // Restricted to the non-constructor case currently to avoid + // having to compare the parameter lists of existing constructors. + if (m_baseClasses.isEmpty() || m_usingMembers.isEmpty() + || hasConstructors()) { + return; + } + + for (const auto &superClass : m_baseClasses) { + // Find any "using base-constructor" directives + if (isUsingMember(superClass, superClass->name(), Access::Protected)) { + // Add to derived class with parameter lists. + const auto ctors = superClass->queryFunctions(FunctionQueryOption::Constructors); + for (const auto &ctor : ctors) { + if (!ctor->isPrivate()) { + addConstructor(AbstractMetaFunction::ConstructorFunction, + ctor->access(), ctor->arguments(), q); + } + } + } + } +} + +static inline bool isSignal(const AbstractMetaFunctionCPtr &f) +{ + return f->isSignal(); +} + +void AbstractMetaClass::fixFunctions(const AbstractMetaClassPtr &klass) +{ + auto *d = klass->d.data(); + if (d->m_functionsFixed) + return; + + d->m_functionsFixed = true; + + AbstractMetaFunctionCList funcs = klass->functions(); + AbstractMetaFunctionCList nonRemovedFuncs; + nonRemovedFuncs.reserve(funcs.size()); + + d->addUsingConstructors(klass); + + for (const auto &f : std::as_const(funcs)) { + // Fishy: Setting up of implementing/declaring/base classes changes + // the applicable modifications; clear cached ones. + std::const_pointer_cast<AbstractMetaFunction>(f)->clearModificationsCache(); + if (!f->isModifiedRemoved()) + nonRemovedFuncs.append(f); + } + + for (const auto &superClassC : d->m_baseClasses) { + for (const auto &pof : superClassC->userAddedPythonOverrides()) { + auto *clonedPof = pof->copy(); + clonedPof->setOwnerClass(klass); + d->m_userAddedPythonOverrides.append(AbstractMetaFunctionCPtr{clonedPof}); + } + + auto superClass = std::const_pointer_cast<AbstractMetaClass>(superClassC); + AbstractMetaClass::fixFunctions(superClass); + // Since we always traverse the complete hierarchy we are only + // interrested in what each super class implements, not what + // we may have propagated from their base classes again. + AbstractMetaFunctionCList superFuncs; + superFuncs = superClass->queryFunctions(FunctionQueryOption::ClassImplements); + // We are not interested in signals as no bindings are generated for them; + // they cause documentation warnings. + superFuncs.erase(std::remove_if(superFuncs.begin(), superFuncs.end(), isSignal), + superFuncs.end()); + const auto virtuals = superClass->queryFunctions(FunctionQueryOption::VirtualInCppFunctions); + superFuncs += virtuals; + + QSet<AbstractMetaFunctionCPtr> funcsToAdd; + for (const auto &sf : std::as_const(superFuncs)) { + if (sf->isModifiedRemoved()) + continue; + + // skip functions added in base classes + if (sf->isUserAdded() && sf->declaringClass() != klass) + continue; + + // Skip base class comparison operators declared as members (free + // operators are added later by traverseOperatorFunction(). + if (sf->isComparisonOperator()) + continue; + + // we generally don't care about private functions, but we have to get the ones that are + // virtual in case they override abstract functions. + bool add = addSuperFunction(sf); + for (const auto &cf : std::as_const(nonRemovedFuncs)) { + AbstractMetaFunctionPtr f(std::const_pointer_cast<AbstractMetaFunction>(cf)); + const AbstractMetaFunction::CompareResult cmp = cf->compareTo(sf.get()); + + if (cmp & AbstractMetaFunction::EqualModifiedName) { + add = false; + if (cmp & AbstractMetaFunction::EqualArguments) { + // Set "override" in case it was not spelled out (since it + // is then not detected by clang parsing). + const auto attributes = cf->cppAttributes(); + if (attributes.testFlag(FunctionAttribute::Virtual) + && !attributes.testFlag(FunctionAttribute::Override) + && !attributes.testFlag(FunctionAttribute::Final)) { + f->setCppAttribute(FunctionAttribute::Override); + } + + if (f->access() != sf->access()) { + qCWarning(lcShiboken, "%s", + qPrintable(msgFunctionVisibilityModified(klass, f.get()))); +#if 0 + // If new visibility is private, we can't + // do anything. If it isn't, then we + // prefer the parent class's visibility + // setting for the function. + if (!f->isPrivate() && !sf->isPrivate()) + f->setVisibility(sf->visibility()); +#endif + // Private overrides of abstract functions have to go into the class or + // the subclasses will not compile as non-abstract classes. + // But they don't need to be implemented, since they can never be called. + if (f->isPrivate()) + f->setFunctionType(AbstractMetaFunction::EmptyFunction); + } + + // Set the class which first declares this function, afawk + f->setDeclaringClass(sf->declaringClass()); + } + + if (cmp & AbstractMetaFunction::EqualDefaultValueOverload) { + AbstractMetaArgumentList arguments; + if (f->arguments().size() < sf->arguments().size()) + arguments = sf->arguments(); + else + arguments = f->arguments(); + //TODO: fix this + //for (int i=0; i<arguments.size(); ++i) + // arguments[i]->setDefaultValueExpression("<#>" + QString()); + } + + + // Otherwise we have function shadowing and we can + // skip the thing... + } else if (cmp & AbstractMetaFunction::EqualName && !sf->isSignal()) { + // In the case of function shadowing where the function name has been altered to + // avoid conflict, we don't copy in the original. + add = false; + } + } + + if (add) + funcsToAdd << sf; + } + + for (const auto &f : std::as_const(funcsToAdd)) { + AbstractMetaFunction *copy = f->copy(); + (*copy) += AbstractMetaFunction::AddedMethod; + funcs.append(AbstractMetaFunctionCPtr(copy)); + } + } + + bool hasPrivateConstructors = false; + bool hasPublicConstructors = false; + // Apply modifications after the declaring class has been set + for (const auto &func : std::as_const(funcs)) { + auto ncFunc = std::const_pointer_cast<AbstractMetaFunction>(func); + for (const auto &mod : func->modifications(klass)) { + if (mod.isRenameModifier()) + ncFunc->setName(mod.renamedToName()); + } + ncFunc->applyTypeModifications(); + + // Make sure class is abstract if one of the functions is + if (func->isAbstract()) { + (*klass) += AbstractMetaClass::Abstract; + (*klass) -= AbstractMetaClass::FinalInTargetLang; + } + + if (func->isConstructor()) { + if (func->isPrivate()) + hasPrivateConstructors = true; + else + hasPublicConstructors = true; + } + + + + // Make sure that we include files for all classes that are in use + addExtraIncludesForFunction(klass, func); + } + + if (hasPrivateConstructors && !hasPublicConstructors) { + (*klass) += AbstractMetaClass::Abstract; + (*klass) -= AbstractMetaClass::FinalInTargetLang; + } + + d->setFunctions(funcs, klass); +} + +bool AbstractMetaClass::needsInheritanceSetup() const +{ + if (d->m_typeEntry != nullptr) { + switch (d->m_typeEntry->type()) { + case TypeEntry::NamespaceType: + case TypeEntry::SmartPointerType: + case TypeEntry::ContainerType: + return false; + default: + break; + } + } + return true; +} + +void AbstractMetaClass::setInheritanceDone(bool b) +{ + d->m_inheritanceDone = b; +} + +bool AbstractMetaClass::inheritanceDone() const +{ + return d->m_inheritanceDone; +} + +/******************************************************************************* + * Other stuff... + */ + +std::optional<AbstractMetaEnumValue> + AbstractMetaClass::findEnumValue(const AbstractMetaClassList &classes, + const QString &name) +{ + const auto lst = QStringView{name}.split(u"::"); + + if (lst.size() > 1) { + const auto &prefixName = lst.at(0); + const auto &enumName = lst.at(1); + if (auto cl = findClass(classes, prefixName)) + return cl->findEnumValue(enumName.toString()); + } + + for (const auto &metaClass : classes) { + auto enumValue = metaClass->findEnumValue(name); + if (enumValue.has_value()) + return enumValue; + } + + qCWarning(lcShiboken, "no matching enum '%s'", qPrintable(name)); + return {}; +} + +/// Searches the list after a class that matches \a name; either as C++, +/// Target language base name or complete Target language package.class name. + +template <class It> +static It findClassHelper(It begin, It end, QAnyStringView name) +{ + if (name.isEmpty() || begin == end) + return end; + + if (asv_contains(name,'.')) { // Search target lang name + for (auto it = begin; it != end; ++it) { + if ((*it)->fullName() == name) + return it; + } + return end; + } + + for (auto it = begin; it != end; ++it) { + if ((*it)->qualifiedCppName() == name) + return it; + } + + if (asv_contains(name, "::")) // Qualified, cannot possibly match name + return end; + + for (auto it = begin; it != end; ++it) { + if ((*it)->name() == name) + return it; + } + + return end; +} + +AbstractMetaClassPtr AbstractMetaClass::findClass(const AbstractMetaClassList &classes, + QAnyStringView name) +{ + auto it =findClassHelper(classes.cbegin(), classes.cend(), name); + return it != classes.cend() ? *it : nullptr; +} + +AbstractMetaClassCPtr AbstractMetaClass::findClass(const AbstractMetaClassCList &classes, + QAnyStringView name) +{ + auto it = findClassHelper(classes.cbegin(), classes.cend(), name); + return it != classes.cend() ? *it : nullptr; +} + +AbstractMetaClassPtr AbstractMetaClass::findClass(const AbstractMetaClassList &classes, + const TypeEntryCPtr &typeEntry) +{ + for (AbstractMetaClassPtr c : classes) { + if (c->typeEntry() == typeEntry) + return c; + } + return nullptr; +} + +AbstractMetaClassCPtr AbstractMetaClass::findClass(const AbstractMetaClassCList &classes, + const TypeEntryCPtr &typeEntry) +{ + for (auto c : classes) { + if (c->typeEntry() == typeEntry) + return c; + } + return nullptr; +} + +/// Returns true if this class is a subclass of the given class +bool inheritsFrom(const AbstractMetaClassCPtr &c, const AbstractMetaClassCPtr &cls) +{ + Q_ASSERT(cls != nullptr); + + if (c == cls || c->templateBaseClass() == cls) + return true; + + return bool(recurseClassHierarchy(c, [cls](const AbstractMetaClassCPtr &c) { + return cls.get() == c.get(); + })); +} + +bool inheritsFrom(const AbstractMetaClassCPtr &c, QAnyStringView name) +{ + if (c->qualifiedCppName() == name) + return true; + + if (c->templateBaseClass() != nullptr + && c->templateBaseClass()->qualifiedCppName() == name) { + return true; + } + + return bool(recurseClassHierarchy(c, [&name](const AbstractMetaClassCPtr &c) { + return c->qualifiedCppName() == name; + })); +} + +AbstractMetaClassCPtr findBaseClass(const AbstractMetaClassCPtr &c, + const QString &qualifiedName) +{ + auto tp = c->templateBaseClass(); + if (tp && tp->qualifiedCppName() == qualifiedName) + return tp; + + return recurseClassHierarchy(c, [&qualifiedName](const AbstractMetaClassCPtr &c) { + return c->qualifiedCppName() == qualifiedName; + }); +} + +// Query functions for generators +bool AbstractMetaClass::isObjectType() const +{ + return d->m_typeEntry->isObject(); +} + +bool AbstractMetaClass::isCopyable() const +{ + if (isNamespace() || d->m_typeEntry->isObject()) + return false; + auto copyable = d->m_typeEntry->copyable(); + return copyable == ComplexTypeEntry::CopyableSet + || (copyable == ComplexTypeEntry::Unknown && isCopyConstructible()); +} + +bool AbstractMetaClass::isValueTypeWithCopyConstructorOnly() const +{ + return d->m_valueTypeWithCopyConstructorOnly; +} + +void AbstractMetaClass::setValueTypeWithCopyConstructorOnly(bool v) +{ + d->m_valueTypeWithCopyConstructorOnly = v; +} + +bool AbstractMetaClass::determineValueTypeWithCopyConstructorOnly(const AbstractMetaClassCPtr &c, + bool avoidProtectedHack) +{ + + if (!c->typeEntry()->isValue()) + return false; + if (c->attributes().testFlag(AbstractMetaClass::HasRejectedDefaultConstructor)) + return false; + const auto ctors = c->queryFunctions(FunctionQueryOption::AnyConstructor); + bool copyConstructorFound = false; + for (const auto &ctor : ctors) { + switch (ctor->functionType()) { + case AbstractMetaFunction::ConstructorFunction: + if (!ctor->isPrivate() && (ctor->isPublic() || !avoidProtectedHack)) + return false; + break; + case AbstractMetaFunction::CopyConstructorFunction: + copyConstructorFound = true; + break; + case AbstractMetaFunction::MoveConstructorFunction: + break; + default: + Q_ASSERT(false); + break; + } + } + return copyConstructorFound; +} + +#ifndef QT_NO_DEBUG_STREAM + +void AbstractMetaClass::format(QDebug &debug) const +{ + if (debug.verbosity() > 2) + debug << static_cast<const void *>(this) << ", "; + debug << '"' << qualifiedCppName(); + if (const auto count = d->m_templateArgs.size()) { + for (qsizetype i = 0; i < count; ++i) + debug << (i ? ',' : '<') << d->m_templateArgs.at(i)->qualifiedCppName(); + debug << '>'; + } + debug << '"'; + if (isNamespace()) + debug << " [namespace]"; + if (attributes().testFlag(AbstractMetaClass::FinalCppClass)) + debug << " [final]"; + if (attributes().testFlag(AbstractMetaClass::Deprecated)) + debug << " [deprecated]"; + + if (d->m_hasPrivateConstructor) + debug << " [private constructor]"; + if (d->m_hasDeletedDefaultConstructor) + debug << " [deleted default constructor]"; + if (d->m_hasDeletedCopyConstructor) + debug << " [deleted copy constructor]"; + if (d->m_hasPrivateDestructor) + debug << " [private destructor]"; + if (d->m_hasProtectedDestructor) + debug << " [protected destructor]"; + if (d->m_hasVirtualDestructor) + debug << " [virtual destructor]"; + if (d->m_valueTypeWithCopyConstructorOnly) + debug << " [value type with copy constructor only]"; + + if (!d->m_baseClasses.isEmpty()) { + debug << ", inherits "; + for (const auto &b : d->m_baseClasses) + debug << " \"" << b->name() << '"'; + } + + if (const qsizetype count = d->m_usingMembers.size()) { + for (qsizetype i = 0; i < count; ++i) { + if (i) + debug << ", "; + debug << d->m_usingMembers.at(i); + } + } + + if (auto templateBase = templateBaseClass()) { + const auto &instantiatedTypes = templateBaseClassInstantiations(); + debug << ", instantiates \"" << templateBase->name(); + for (qsizetype i = 0, count = instantiatedTypes.size(); i < count; ++i) + debug << (i ? ',' : '<') << instantiatedTypes.at(i).name(); + debug << ">\""; + } + if (const auto count = d->m_propertySpecs.size()) { + debug << ", properties (" << count << "): ["; + for (qsizetype i = 0; i < count; ++i) { + if (i) + debug << ", "; + d->m_propertySpecs.at(i).formatDebug(debug); + } + debug << ']'; + } +} + +void AbstractMetaClass::formatMembers(QDebug &debug) const +{ + if (!d->m_enums.isEmpty()) + debug << ", enums[" << d->m_enums.size() << "]=" << d->m_enums; + if (!d->m_functions.isEmpty()) { + const auto count = d->m_functions.size(); + debug << ", functions=[" << count << "]("; + for (qsizetype i = 0; i < count; ++i) { + if (i) + debug << ", "; + d->m_functions.at(i)->formatDebugBrief(debug); + } + debug << ')'; + } + if (const auto count = d->m_fields.size()) { + debug << ", fields=[" << count << "]("; + for (qsizetype i = 0; i < count; ++i) { + if (i) + debug << ", "; + d->m_fields.at(i).formatDebug(debug); + } + debug << ')'; + } +} + +SourceLocation AbstractMetaClass::sourceLocation() const +{ + return d->m_sourceLocation; +} + +void AbstractMetaClass::setSourceLocation(const SourceLocation &sourceLocation) +{ + d->m_sourceLocation = sourceLocation; +} + +AbstractMetaClassCList allBaseClasses(const AbstractMetaClassCPtr metaClass) +{ + AbstractMetaClassCList result; + recurseClassHierarchy(metaClass, [&result] (const AbstractMetaClassCPtr &c) { + if (!result.contains(c)) + result.append(c); + return false; + }); + result.removeFirst(); // remove self + return result; +} + +QDebug operator<<(QDebug debug, const UsingMember &d) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "UsingMember(" << d.access << ' ' + << d.baseClass->qualifiedCppName() << "::" << d.memberName << ')'; + return debug; +} + +void formatMetaClass(QDebug &ddebug, const AbstractMetaClass *ac) +{ + QDebugStateSaver saver(ddebug); + ddebug.noquote(); + ddebug.nospace(); + ddebug << "AbstractMetaClass("; + if (ac != nullptr) { + ac->format(ddebug); + if (ddebug.verbosity() > 2) + ac->formatMembers(ddebug); + } else { + ddebug << '0'; + } + ddebug << ')'; +} + +QDebug operator<<(QDebug d, const AbstractMetaClassCPtr &ac) +{ + formatMetaClass(d, ac.get()); + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaClassPtr &ac) +{ + formatMetaClass(d, ac.get()); + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaClass *ac) +{ + formatMetaClass(d, ac); + return d; +} + +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang.h b/sources/shiboken6/ApiExtractor/abstractmetalang.h new file mode 100644 index 000000000..3dc876690 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetalang.h @@ -0,0 +1,393 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETALANG_H +#define ABSTRACTMETALANG_H + +#include "abstractmetalang_enums.h" +#include "abstractmetalang_typedefs.h" +#include "enclosingclassmixin.h" +#include "typesystem_typedefs.h" + +#include <QtCore/qobjectdefs.h> +#include <QtCore/QScopedPointer> +#include <QtCore/QStringList> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +enum class Access; +class AbstractMetaClassPrivate; +class ComplexTypeEntry; +class Documentation; +class EnumTypeEntry; +class QPropertySpec; +class SourceLocation; +struct UsingMember; + +class AbstractMetaClass : public EnclosingClassMixin +{ + Q_GADGET +public: + Q_DISABLE_COPY_MOVE(AbstractMetaClass) + + enum CppWrapperFlag { + NoCppWrapper = 0x0, + CppProtectedHackWrapper = 0x1,// Make protected functions accessible + CppVirtualMethodWrapper = 0x2 // Need C++ wrapper for calling Python overrides + }; + Q_DECLARE_FLAGS(CppWrapper, CppWrapperFlag) + + enum Attribute { + None = 0x00000000, + + Abstract = 0x00000001, + FinalInTargetLang = 0x00000002, + + HasRejectedConstructor = 0x00000010, + HasRejectedDefaultConstructor = 0x00000020, + + FinalCppClass = 0x00000100, + Deprecated = 0x00000200, + Struct = 0x00000400 + }; + Q_DECLARE_FLAGS(Attributes, Attribute) + Q_FLAG(Attribute) + + Attributes attributes() const; + void setAttributes(Attributes attributes); + + void operator+=(Attribute attribute); + void operator-=(Attribute attribute); + + bool isFinalInTargetLang() const; + bool isAbstract() const; + + AbstractMetaClass(); + ~AbstractMetaClass(); + + const AbstractMetaFunctionCList &functions() const; + const AbstractMetaFunctionCList &userAddedPythonOverrides() const; + void setFunctions(const AbstractMetaFunctionCList &functions); + static void addFunction(const AbstractMetaClassPtr &klass, + const AbstractMetaFunctionCPtr &function); + bool hasFunction(const QString &str) const; + AbstractMetaFunctionCPtr findFunction(QAnyStringView functionName) const; + AbstractMetaFunctionCList findFunctions(QAnyStringView functionName) const; + AbstractMetaFunctionCPtr findOperatorBool() const; + // Find a Qt-style isNull() method suitable for nb_bool + AbstractMetaFunctionCPtr findQtIsNullMethod() const; + bool hasSignal(const AbstractMetaFunction *f) const; + + bool hasConstructors() const; + AbstractMetaFunctionCPtr copyConstructor() const; + bool hasCopyConstructor() const; + bool hasPrivateCopyConstructor() const; + + static void addDefaultConstructor(const AbstractMetaClassPtr &klass); + static void addDefaultCopyConstructor(const AbstractMetaClassPtr &klass); + + bool hasNonPrivateConstructor() const; + void setHasNonPrivateConstructor(bool value); + + bool hasPrivateConstructor() const; + void setHasPrivateConstructor(bool value); + + bool hasDeletedDefaultConstructor() const; + void setHasDeletedDefaultConstructor(bool value); + + bool hasDeletedCopyConstructor() const; + void setHasDeletedCopyConstructor(bool value); + + bool hasPrivateDestructor() const; + void setHasPrivateDestructor(bool value); + + bool hasProtectedDestructor() const; + void setHasProtectedDestructor(bool value); + + bool hasVirtualDestructor() const; + void setHasVirtualDestructor(bool value); + + bool isDefaultConstructible() const; + bool isImplicitlyDefaultConstructible() const; + bool canAddDefaultConstructor() const; + + bool isCopyConstructible() const; + bool isImplicitlyCopyConstructible() const; + bool canAddDefaultCopyConstructor() const; + + static void addSynthesizedComparisonOperators(const AbstractMetaClassPtr &c); + + bool generateExceptionHandling() const; + + CppWrapper cppWrapper() const; + + const UsingMembers &usingMembers() const; + void addUsingMember(const UsingMember &um); + bool isUsingMember(const AbstractMetaClassCPtr &c, const QString &memberName, + Access minimumAccess) const; + bool hasUsingMemberFor(const QString &memberName) const; + + AbstractMetaFunctionCList queryFunctionsByName(const QString &name) const; + static bool queryFunction(const AbstractMetaFunction *f, FunctionQueryOptions query); + static AbstractMetaFunctionCList queryFunctionList(const AbstractMetaFunctionCList &list, + FunctionQueryOptions query); + static AbstractMetaFunctionCPtr queryFirstFunction(const AbstractMetaFunctionCList &list, + FunctionQueryOptions query); + + AbstractMetaFunctionCList queryFunctions(FunctionQueryOptions query) const; + AbstractMetaFunctionCList functionsInTargetLang() const; + AbstractMetaFunctionCList cppSignalFunctions() const; + AbstractMetaFunctionCList implicitConversions() const; + + /** + * Retrieves all class' operator overloads that meet + * query criteria defined with the OperatorQueryOption + * enum. + * /param query composition of OperatorQueryOption enum values + * /return list of operator overload methods that meet the + * query criteria + */ + AbstractMetaFunctionCList operatorOverloads(OperatorQueryOptions query) const; + + bool hasArithmeticOperatorOverload() const; + bool hasIncDecrementOperatorOverload() const; + bool hasBitwiseOperatorOverload() const; + bool hasComparisonOperatorOverload() const; + bool hasLogicalOperatorOverload() const; + + const AbstractMetaFieldList &fields() const; + AbstractMetaFieldList &fields(); + void setFields(const AbstractMetaFieldList &fields); + void addField(const AbstractMetaField &field); + bool hasStaticFields() const; + + std::optional<AbstractMetaField> findField(QStringView name) const; + + const AbstractMetaEnumList &enums() const; + AbstractMetaEnumList &enums(); + void setEnums(const AbstractMetaEnumList &enums); + void addEnum(const AbstractMetaEnum &e); + + std::optional<AbstractMetaEnum> findEnum(const QString &enumName) const; + std::optional<AbstractMetaEnumValue> findEnumValue(const QString &enumName) const; + void getEnumsToBeGenerated(AbstractMetaEnumList *enumList) const; + void getEnumsFromInvisibleNamespacesToBeGenerated(AbstractMetaEnumList *enumList) const; + + void getFunctionsFromInvisibleNamespacesToBeGenerated(AbstractMetaFunctionCList *funcList) const; + + QString fullName() const; + + /** + * Retrieves the class name without any namespace/scope information. + * /return the class name without scope information + */ + QString name() const; + + const Documentation &documentation() const; + void setDocumentation(const Documentation& doc); + + QString baseClassName() const; + + AbstractMetaClassCPtr defaultSuperclass() const; // Attribute "default-superclass" + void setDefaultSuperclass(const AbstractMetaClassPtr &s); + + AbstractMetaClassCPtr baseClass() const; + const AbstractMetaClassCList &baseClasses() const; + // base classes including defaultSuperclass + AbstractMetaClassCList typeSystemBaseClasses() const; + // Recursive list of all base classes including defaultSuperclass + AbstractMetaClassCList allTypeSystemAncestors() const; + + void addBaseClass(const AbstractMetaClassCPtr &base_class); + void setBaseClass(const AbstractMetaClassCPtr &base_class); + + /** + * \return the namespace from another package which this namespace extends. + */ + AbstractMetaClassCPtr extendedNamespace() const; + void setExtendedNamespace(const AbstractMetaClassCPtr &e); + + const AbstractMetaClassCList &innerClasses() const; + void addInnerClass(const AbstractMetaClassPtr &cl); + void setInnerClasses(const AbstractMetaClassCList &innerClasses); + + QString package() const; + + bool isNamespace() const; + bool isInvisibleNamespace() const; + bool isInlineNamespace() const; + + bool isQtNamespace() const; + + QString qualifiedCppName() const; + + bool hasSignals() const; + + /** + * Says if the class that declares or inherits a virtual function. + * \return true if the class implements or inherits any virtual methods + */ + bool isPolymorphic() const; + + /** + * Tells if this class has one or more fields (member variables) that are protected. + * \return true if the class has protected fields. + */ + bool hasProtectedFields() const; + + + const TypeEntryCList &templateArguments() const; + void setTemplateArguments(const TypeEntryCList &); + + // only valid during metabuilder's run + const QStringList &baseClassNames() const; + void setBaseClassNames(const QStringList &names); + + ComplexTypeEntryCPtr typeEntry() const; + ComplexTypeEntryPtr typeEntry(); + void setTypeEntry(const ComplexTypeEntryPtr &type); + + /// Returns the global hash function as found by the code parser + QString hashFunction() const; + void setHashFunction(const QString &); + + /// Returns whether the class has a qHash() overload. Currently unused, + /// specified in type system. + bool hasHashFunction() const; + + const QList<QPropertySpec> &propertySpecs() const; + void addPropertySpec(const QPropertySpec &spec); + void setPropertyDocumentation(const QString &name, const Documentation &doc); + + // Helpers to search whether a functions is a property setter/getter/reset + enum class PropertyFunction + { + Read, + Write, + Reset, + Notify + }; + struct PropertyFunctionSearchResult + { + qsizetype index; + PropertyFunction function; + }; + + PropertyFunctionSearchResult searchPropertyFunction(const QString &name) const; + + std::optional<QPropertySpec> propertySpecByName(const QString &name) const; + + /// Returns a list of conversion operators for this class. The conversion + /// operators are defined in other classes of the same module. + const AbstractMetaFunctionCList &externalConversionOperators() const; + /// Adds a converter operator for this class. + void addExternalConversionOperator(const AbstractMetaFunctionCPtr &conversionOp); + /// Returns true if this class has any converter operators defined elsewhere. + bool hasExternalConversionOperators() const; + + void sortFunctions(); + + AbstractMetaClassCPtr templateBaseClass() const; + void setTemplateBaseClass(const AbstractMetaClassCPtr &cls); + + bool hasTemplateBaseClassInstantiations() const; + const AbstractMetaTypeList &templateBaseClassInstantiations() const; + void setTemplateBaseClassInstantiations(const AbstractMetaTypeList& instantiations); + + void setTypeDef(bool typeDef); + bool isTypeDef() const; + + bool isStream() const; + void setStream(bool stream); + + bool hasToStringCapability() const; + void setToStringCapability(bool value, uint indirections = 0); + + uint toStringCapabilityIndirections() const; + + bool deleteInMainThread() const; + + // Query functions for generators + bool isObjectType() const; + bool isCopyable() const; + bool isValueTypeWithCopyConstructorOnly() const; + void setValueTypeWithCopyConstructorOnly(bool v); + static bool determineValueTypeWithCopyConstructorOnly(const AbstractMetaClassCPtr &c, + bool avoidProtectedHack); + + static AbstractMetaClassPtr findClass(const AbstractMetaClassList &classes, + QAnyStringView name); + static AbstractMetaClassCPtr findClass(const AbstractMetaClassCList &classes, + QAnyStringView name); + static AbstractMetaClassPtr findClass(const AbstractMetaClassList &classes, + const TypeEntryCPtr &typeEntry); + static AbstractMetaClassCPtr findClass(const AbstractMetaClassCList &classes, + const TypeEntryCPtr &typeEntry); + AbstractMetaClassCPtr findBaseClass(const QString &qualifiedName) const; + + static std::optional<AbstractMetaEnumValue> findEnumValue(const AbstractMetaClassList &classes, + const QString &string); + + SourceLocation sourceLocation() const; + void setSourceLocation(const SourceLocation &sourceLocation); + + // For AbstractMetaBuilder + static void fixFunctions(const AbstractMetaClassPtr &klass); + bool needsInheritanceSetup() const; + void setInheritanceDone(bool b); + bool inheritanceDone() const; + + template <class Function> + void invisibleNamespaceRecursion(Function f) const; + +private: +#ifndef QT_NO_DEBUG_STREAM + void format(QDebug &d) const; + void formatMembers(QDebug &d) const; + friend QDebug operator<<(QDebug d, const AbstractMetaClassCPtr &ac); + friend QDebug operator<<(QDebug d, const AbstractMetaClassPtr &ac); + friend QDebug operator<<(QDebug d, const AbstractMetaClass *ac); + friend void formatMetaClass(QDebug &, const AbstractMetaClass *); +#endif + + QScopedPointer<AbstractMetaClassPrivate> d; +}; + +inline bool AbstractMetaClass::isAbstract() const +{ + return attributes().testFlag(Abstract); +} + +template <class Function> +void AbstractMetaClass::invisibleNamespaceRecursion(Function f) const +{ + for (const auto &ic : innerClasses()) { + if (ic->isInvisibleNamespace()) { + f(ic); + ic->invisibleNamespaceRecursion(f); + } + } +} + +bool inheritsFrom(const AbstractMetaClassCPtr &c, const AbstractMetaClassCPtr &other); +bool inheritsFrom(const AbstractMetaClassCPtr &c, QAnyStringView name); +inline bool isQObject(const AbstractMetaClassCPtr &c) +{ + return inheritsFrom(c, "QObject"); +} + +AbstractMetaClassCPtr findBaseClass(const AbstractMetaClassCPtr &c, + const QString &qualifiedName); + +/// Return type entry of the base class that declares the parent management +TypeEntryCPtr parentManagementEntry(const AbstractMetaClassCPtr &klass); +inline bool hasParentManagement(const AbstractMetaClassCPtr &c) +{ return bool(parentManagementEntry(c)); } + +AbstractMetaClassCList allBaseClasses(const AbstractMetaClassCPtr metaClass); + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractMetaClass::CppWrapper); + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractMetaClass::Attributes); + +#endif // ABSTRACTMETALANG_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h b/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h new file mode 100644 index 000000000..9047c6bcd --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetalang_enums.h @@ -0,0 +1,50 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETALANG_ENUMS_H +#define ABSTRACTMETALANG_ENUMS_H + +#include <QtCore/QFlags> + +enum class FunctionQueryOption { + AnyConstructor = 0x0000001, // Any constructor (copy/move) + Constructors = 0x0000002, // Constructors except copy/move + CopyConstructor = 0x0000004, // Only copy constructors + //Destructors = 0x0000002, // Only destructors. Not included in class. + ClassImplements = 0x0000020, // Only functions implemented by the current class + StaticFunctions = 0x0000080, // Only static functions + Signals = 0x0000100, // Only signals + NormalFunctions = 0x0000200, // Only functions that aren't signals + Visible = 0x0000400, // Only public and protected functions + NonStaticFunctions = 0x0004000, // No static functions + Empty = 0x0008000, // Empty overrides of abstract functions + Invisible = 0x0010000, // Only private functions + VirtualInCppFunctions = 0x0020000, // Only functions that are virtual in C++ + NotRemoved = 0x0400000, // Only functions that have not been removed + OperatorOverloads = 0x2000000, // Only functions that are operator overloads + GenerateExceptionHandling = 0x4000000, + GetAttroFunction = 0x8000000, + SetAttroFunction = 0x10000000 +}; + +Q_DECLARE_FLAGS(FunctionQueryOptions, FunctionQueryOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(FunctionQueryOptions) + +enum class OperatorQueryOption { + ArithmeticOp = 0x01, // Arithmetic: +, -, *, /, %, +=, -=, *=, /=, %=, unary+, unary- + IncDecrementOp = 0x02, // ++, -- + BitwiseOp = 0x04, // Bitwise: <<, <<=, >>, >>=, ~, &, &=, |, |=, ^, ^= + ComparisonOp = 0x08, // Comparison: <, <=, >, >=, !=, == + // Comparing to instances of owner class: <, <=, >, >=, !=, == + // (bool operator==(QByteArray,QByteArray) but not bool operator==(QByteArray,const char *) + SymmetricalComparisonOp = 0x10, + LogicalOp = 0x20, // Logical: !, &&, || + ConversionOp = 0x40, // Conversion: operator [const] TYPE() + SubscriptionOp = 0x80, // Subscription: [] + AssignmentOp = 0x100 // Assignment: = +}; + +Q_DECLARE_FLAGS(OperatorQueryOptions, OperatorQueryOption) +Q_DECLARE_OPERATORS_FOR_FLAGS(OperatorQueryOptions) + +#endif // ABSTRACTMETALANG_ENUMS_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang_helpers.h b/sources/shiboken6/ApiExtractor/abstractmetalang_helpers.h new file mode 100644 index 000000000..2a053ceaf --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetalang_helpers.h @@ -0,0 +1,35 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETALANG_HELPERS_H +#define ABSTRACTMETALANG_HELPERS_H + +#include "abstractmetalang_typedefs.h" + +template <class MetaClass> +std::shared_ptr<MetaClass> findByName(const QList<std::shared_ptr<MetaClass> > &haystack, + QStringView needle) +{ + for (const auto &c : haystack) { + if (c->name() == needle) + return c; + } + return {}; +} + +// Helper for recursing the base classes of an AbstractMetaClass. +// Returns the class for which the predicate is true. +template <class Predicate> +AbstractMetaClassCPtr recurseClassHierarchy(const AbstractMetaClassCPtr &klass, + Predicate pred) +{ + if (pred(klass)) + return klass; + for (auto base : klass->baseClasses()) { + if (auto r = recurseClassHierarchy(base, pred)) + return r; + } + return {}; +} + +#endif // ABSTRACTMETALANG_HELPERS_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h b/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h new file mode 100644 index 000000000..802f549cf --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetalang_typedefs.h @@ -0,0 +1,36 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETALANG_TYPEDEFS_H +#define ABSTRACTMETALANG_TYPEDEFS_H + +#include <QtCore/QList> + +#include <memory> + +class AbstractMetaClass; +class AbstractMetaField; +class AbstractMetaArgument; +class AbstractMetaEnum; +class AbstractMetaEnumValue; +class AbstractMetaFunction; +class AbstractMetaType; +struct UsingMember; + +using AbstractMetaFunctionPtr = std::shared_ptr<AbstractMetaFunction>; +using AbstractMetaFunctionCPtr = std::shared_ptr<const AbstractMetaFunction>; +using AbstractMetaClassPtr = std::shared_ptr<AbstractMetaClass>; +using AbstractMetaClassCPtr = std::shared_ptr<const AbstractMetaClass>; + +using AbstractMetaArgumentList = QList<AbstractMetaArgument>; +using AbstractMetaClassList = QList<AbstractMetaClassPtr>; +using AbstractMetaClassCList = QList<AbstractMetaClassCPtr>; +using AbstractMetaEnumList = QList<AbstractMetaEnum>; +using AbstractMetaEnumValueList = QList<AbstractMetaEnumValue>; +using AbstractMetaFieldList = QList<AbstractMetaField>; +using AbstractMetaFunctionRawPtrList = QList<AbstractMetaFunction *>; +using AbstractMetaFunctionCList = QList<AbstractMetaFunctionCPtr>; +using AbstractMetaTypeList = QList<AbstractMetaType>; +using UsingMembers = QList<UsingMember>; + +#endif // ABSTRACTMETALANG_TYPEDEFS_H diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.cpp b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp new file mode 100644 index 000000000..3ec07509d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.cpp @@ -0,0 +1,1120 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractmetatype.h" +#include "abstractmetabuilder.h" +#include "abstractmetalang.h" +#include "messages.h" +#include "typedatabase.h" +#include "containertypeentry.h" +#include "enumtypeentry.h" +#include "flagstypeentry.h" + +#include "qtcompat.h" +#include "typeinfo.h" + +#ifndef QT_NO_DEBUG_STREAM +# include <QtCore/QDebug> +#endif + +#include <QtCore/QHash> +#include <QtCore/QSharedData> +#include <QtCore/QStack> + +#include <memory> + +using namespace Qt::StringLiterals; + +using AbstractMetaTypeCPtr = std::shared_ptr<const AbstractMetaType>; + +const QSet<QString> &AbstractMetaType::cppFloatTypes() +{ + static const QSet<QString> result{u"double"_s, u"float"_s}; + return result; +} + +const QSet<QString> &AbstractMetaType::cppSignedCharTypes() +{ + static const QSet<QString> result{u"char"_s, u"signed char"_s}; + return result; +} + +const QSet<QString> &AbstractMetaType::cppUnsignedCharTypes() +{ + static const QSet<QString> result{u"unsigned char"_s}; + return result; +} + +const QSet<QString> &AbstractMetaType::cppCharTypes() +{ + static const QSet<QString> result = cppSignedCharTypes() | cppUnsignedCharTypes(); + return result; +} + +const QSet<QString> &AbstractMetaType::cppSignedIntTypes() +{ + static QSet<QString> result; + if (result.isEmpty()) { + result = {u"char"_s, u"signed char"_s, u"short"_s, u"short int"_s, + u"signed short"_s, u"signed short int"_s, + u"int"_s, u"signed int"_s, + u"long"_s, u"long int"_s, + u"signed long"_s, u"signed long int"_s, + u"long long"_s, u"long long int"_s, + u"signed long long int"_s, + u"ptrdiff_t"_s}; + result |= cppSignedCharTypes(); + } + return result; +} + +const QSet<QString> &AbstractMetaType::cppUnsignedIntTypes() +{ + static QSet<QString> result; + if (result.isEmpty()) { + result = {u"unsigned short"_s, u"unsigned short int"_s, + u"unsigned"_s, u"unsigned int"_s, + u"unsigned long"_s, u"unsigned long int"_s, + u"unsigned long long"_s, + u"unsigned long long int"_s, + u"size_t"_s}; + result |= cppUnsignedCharTypes(); + } + return result; +} + +const QSet<QString> &AbstractMetaType::cppIntegralTypes() +{ + static QSet<QString> result; + if (result.isEmpty()) { + result |= cppSignedIntTypes(); + result |= cppUnsignedIntTypes(); + result.insert(u"bool"_s); + } + return result; +} + +const QSet<QString> &AbstractMetaType::cppPrimitiveTypes() +{ + static QSet<QString> result; + if (result.isEmpty()) { + result |= cppIntegralTypes(); + result |= cppFloatTypes(); + result.insert(u"wchar_t"_s); + } + return result; +} + +class AbstractMetaTypeData : public QSharedData +{ +public: + AbstractMetaTypeData(const TypeEntryCPtr &t); + + int actualIndirections() const; + bool passByConstRef() const; + bool passByValue() const; + AbstractMetaType::TypeUsagePattern determineUsagePattern() const; + bool hasTemplateChildren() const; + QString formatSignature(bool minimal) const; + QString formatPythonSignature() const; + bool isEquivalent(const AbstractMetaTypeData &rhs) const; + bool equals(const AbstractMetaTypeData &rhs) const; + QStringList instantiationCppSignatures() const; + + template <class Predicate> + bool generateOpaqueContainer(Predicate p) const; + + TypeEntryCPtr m_typeEntry; + AbstractMetaTypeList m_instantiations; + mutable QString m_cachedCppSignature; + mutable QString m_cachedPythonSignature; + QString m_originalTypeDescription; + + int m_arrayElementCount = -1; + AbstractMetaTypeCPtr m_arrayElementType; + AbstractMetaTypeCPtr m_originalTemplateType; + AbstractMetaTypeCPtr m_viewOn; + AbstractMetaType::Indirections m_indirections; + + AbstractMetaType::TypeUsagePattern m_pattern = AbstractMetaType::VoidPattern; + uint m_constant : 1; + uint m_volatile : 1; + uint m_signaturesDirty : 1; + uint m_reserved : 29; // unused + + ReferenceType m_referenceType = NoReference; + AbstractMetaTypeList m_children; +}; + +AbstractMetaTypeData::AbstractMetaTypeData(const TypeEntryCPtr &t) : + m_typeEntry(t), + m_constant(false), + m_volatile(false), + m_signaturesDirty(true), + m_reserved(0) +{ +} + +QStringList AbstractMetaTypeData::instantiationCppSignatures() const +{ + QStringList result; + for (const auto &i : m_instantiations) + result.append(i.cppSignature()); + return result; +} + +AbstractMetaType::AbstractMetaType(const TypeEntryCPtr &t) : + d(new AbstractMetaTypeData(t)) +{ + Q_ASSERT(t); +} + +AbstractMetaType::AbstractMetaType() +{ + *this = AbstractMetaType::createVoid(); +} + +AbstractMetaType &AbstractMetaType::operator=(const AbstractMetaType &) = default; + +AbstractMetaType::AbstractMetaType(const AbstractMetaType &rhs) = default; + +AbstractMetaType::AbstractMetaType(AbstractMetaType &&) noexcept = default; + +AbstractMetaType &AbstractMetaType::operator=(AbstractMetaType &&) noexcept = default; + +AbstractMetaType::~AbstractMetaType() = default; + +QString AbstractMetaType::package() const +{ + return d->m_typeEntry->targetLangPackage(); +} + +QString AbstractMetaType::name() const +{ + return d->m_typeEntry->targetLangEntryName(); +} + +QString AbstractMetaType::fullName() const +{ + return d->m_typeEntry->qualifiedTargetLangName(); +} + +void AbstractMetaType::setTypeUsagePattern(AbstractMetaType::TypeUsagePattern pattern) +{ + if (d->m_pattern != pattern) + d->m_pattern = pattern; +} + +AbstractMetaType::TypeUsagePattern AbstractMetaType::typeUsagePattern() const +{ + return d->m_pattern; +} + +bool AbstractMetaType::hasInstantiations() const +{ + return !d->m_instantiations.isEmpty(); +} + +void AbstractMetaType::addInstantiation(const AbstractMetaType &inst) +{ + d->m_instantiations << inst; +} + +void AbstractMetaType::setInstantiations(const AbstractMetaTypeList &insts) +{ + if (insts != d->m_instantiations) + d->m_instantiations = insts; +} + +const AbstractMetaTypeList &AbstractMetaType::instantiations() const +{ + return d->m_instantiations; +} + +QStringList AbstractMetaType::instantiationCppSignatures() const +{ + return d->instantiationCppSignatures(); +} + +// For applying the <array> function argument modification: change into a type +// where "int *" becomes "int[]". +bool AbstractMetaType::applyArrayModification(QString *errorMessage) +{ + + if (d->m_pattern == AbstractMetaType::NativePointerAsArrayPattern) { + *errorMessage = u"<array> modification already applied."_s; + return false; + } + if (d->m_arrayElementType) { + QTextStream(errorMessage) << "The type \"" << cppSignature() + << "\" is an array of " << d->m_arrayElementType->name() << '.'; + return false; + } + if (d->m_indirections.isEmpty()) { + QTextStream(errorMessage) << "The type \"" << cppSignature() + << "\" does not have indirections."; + return false; + } + // Element type to be used for ArrayHandle<>, strip constness. + + auto elementType = new AbstractMetaType(*this); + auto indir = indirectionsV(); + indir.pop_front(); + elementType->setIndirectionsV(indir); + elementType->setConstant(false); + elementType->setVolatile(false); + elementType->decideUsagePattern(); + d->m_arrayElementType.reset(elementType); + d->m_pattern = AbstractMetaType::NativePointerAsArrayPattern; + return true; +} + +TypeEntryCPtr AbstractMetaType::typeEntry() const +{ + return d->m_typeEntry; +} + +void AbstractMetaType::setTypeEntry(const TypeEntryCPtr &type) +{ + if (d->m_typeEntry != type) + d->m_typeEntry = type; +} + +void AbstractMetaType::setOriginalTypeDescription(const QString &otd) +{ + if (d->m_originalTypeDescription != otd) + d->m_originalTypeDescription = otd; +} + +QString AbstractMetaType::originalTypeDescription() const +{ + return d->m_originalTypeDescription; +} + +void AbstractMetaType::setOriginalTemplateType(const AbstractMetaType &type) +{ + if (!d->m_originalTemplateType || *d->m_originalTemplateType != type) + d->m_originalTemplateType.reset(new AbstractMetaType(type)); +} + +const AbstractMetaType *AbstractMetaType::originalTemplateType() const +{ + return d->m_originalTemplateType.get(); +} + +AbstractMetaType AbstractMetaType::getSmartPointerInnerType() const +{ + Q_ASSERT(isSmartPointer()); + Q_ASSERT(!d->m_instantiations.isEmpty()); + AbstractMetaType innerType = d->m_instantiations.at(0); + return innerType; +} + +QString AbstractMetaType::getSmartPointerInnerTypeName() const +{ + Q_ASSERT(isSmartPointer()); + return getSmartPointerInnerType().name(); +} + +AbstractMetaTypeList AbstractMetaType::nestedArrayTypes() const +{ + AbstractMetaTypeList result; + switch (d->m_pattern) { + case ArrayPattern: + for (AbstractMetaType t = *this; t.typeUsagePattern() == ArrayPattern; ) { + const AbstractMetaType *elt = t.arrayElementType(); + result.append(*elt); + t = *elt; + } + break; + case NativePointerAsArrayPattern: + result.append(*d->m_arrayElementType.get()); + break; + default: + break; + } + return result; +} + +bool AbstractMetaTypeData::passByConstRef() const +{ + return m_constant && m_referenceType == LValueReference && m_indirections.isEmpty(); +} + +bool AbstractMetaType::passByConstRef() const +{ + return d->passByConstRef(); +} + +bool AbstractMetaTypeData::passByValue() const +{ + return m_referenceType == NoReference && m_indirections.isEmpty(); +} + +bool AbstractMetaType::passByValue() const +{ + return d->passByValue(); +} + +bool AbstractMetaType::useStdMove() const +{ + return (isUniquePointer() && d->passByValue()) + || d->m_referenceType == RValueReference; +} + +ReferenceType AbstractMetaType::referenceType() const +{ + return d->m_referenceType; +} + +void AbstractMetaType::setReferenceType(ReferenceType ref) +{ + if (d->m_referenceType != ref) { + d->m_referenceType = ref; + d->m_signaturesDirty = true; + } +} + +int AbstractMetaTypeData::actualIndirections() const +{ + return m_indirections.size() + (m_referenceType == LValueReference ? 1 : 0); +} + +int AbstractMetaType::actualIndirections() const +{ + return d->actualIndirections(); +} + +AbstractMetaType::Indirections AbstractMetaType::indirectionsV() const +{ + return d->m_indirections; +} + +void AbstractMetaType::setIndirectionsV(const AbstractMetaType::Indirections &i) +{ + if (d->m_indirections != i) { + d->m_indirections = i; + d->m_signaturesDirty = true; + } +} + +void AbstractMetaType::clearIndirections() +{ + if (!d->m_indirections.isEmpty()) { + d->m_indirections.clear(); + d->m_signaturesDirty = true; + } +} + +int AbstractMetaType::indirections() const +{ + return d->m_indirections.size(); +} + +void AbstractMetaType::setIndirections(int indirections) +{ + const Indirections newValue(indirections, Indirection::Pointer); + if (d->m_indirections != newValue) { + d->m_indirections = newValue; + d->m_signaturesDirty = true; + } +} + +void AbstractMetaType::addIndirection(Indirection i) +{ + d->m_indirections.append(i); +} + +void AbstractMetaType::setArrayElementCount(int n) +{ + if (d->m_arrayElementCount != n) { + d->m_arrayElementCount = n; + d->m_signaturesDirty = true; + } +} + +int AbstractMetaType::arrayElementCount() const +{ + return d->m_arrayElementCount; +} + +const AbstractMetaType *AbstractMetaType::arrayElementType() const +{ + return d->m_arrayElementType.get(); +} + +void AbstractMetaType::setArrayElementType(const AbstractMetaType &t) +{ + if (!d->m_arrayElementType || *d->m_arrayElementType != t) { + d->m_arrayElementType.reset(new AbstractMetaType(t)); + d->m_signaturesDirty = true; + } +} + +AbstractMetaType AbstractMetaType::plainType() const +{ + AbstractMetaType result = *this; + result.clearIndirections(); + result.setReferenceType(NoReference); + result.setConstant(false); + result.setVolatile(false); + result.decideUsagePattern(); + return result; +} + +QString AbstractMetaType::cppSignature() const +{ + const AbstractMetaTypeData *cd = d.constData(); + if (cd->m_cachedCppSignature.isEmpty() || cd->m_signaturesDirty) + cd->m_cachedCppSignature = formatSignature(false); + return cd->m_cachedCppSignature; +} + +QString AbstractMetaType::pythonSignature() const +{ + // PYSIDE-921: Handle container returntypes correctly. + // This is now a clean reimplementation. + const AbstractMetaTypeData *cd = d.constData(); + if (cd->m_cachedPythonSignature.isEmpty() || cd->m_signaturesDirty) + cd->m_cachedPythonSignature = formatPythonSignature(); + return cd->m_cachedPythonSignature; +} + +AbstractMetaType::TypeUsagePattern AbstractMetaTypeData::determineUsagePattern() const +{ + if (m_typeEntry->isTemplateArgument()) + return AbstractMetaType::TemplateArgument; + + if (m_typeEntry->type() == TypeEntry::ConstantValueType) + return AbstractMetaType::NonTypeTemplateArgument; + + if (m_typeEntry->isPrimitive() && (actualIndirections() == 0 || passByConstRef())) + return AbstractMetaType::PrimitivePattern; + + if (m_typeEntry->isVoid()) { + return m_arrayElementCount < 0 && m_referenceType == NoReference + && m_indirections.isEmpty() && m_constant == 0 && m_volatile == 0 + ? AbstractMetaType::VoidPattern : AbstractMetaType::NativePointerPattern; + } + + if (m_typeEntry->isVarargs()) + return AbstractMetaType::VarargsPattern; + + if (m_typeEntry->isEnum() && (actualIndirections() == 0 || passByConstRef())) + return AbstractMetaType::EnumPattern; + + if (m_typeEntry->isObject()) { + if (m_indirections.isEmpty() && m_referenceType == NoReference) + return AbstractMetaType::ValuePattern; + return AbstractMetaType::ObjectPattern; + } + + if (m_typeEntry->isContainer() && m_indirections.isEmpty()) + return AbstractMetaType::ContainerPattern; + + if (m_typeEntry->isSmartPointer() && m_indirections.isEmpty()) + return AbstractMetaType::SmartPointerPattern; + + if (m_typeEntry->isFlags() && (actualIndirections() == 0 || passByConstRef())) + return AbstractMetaType::FlagsPattern; + + if (m_typeEntry->isArray()) + return AbstractMetaType::ArrayPattern; + + if (m_typeEntry->isValue()) + return m_indirections.size() == 1 ? AbstractMetaType::ValuePointerPattern : AbstractMetaType::ValuePattern; + + return AbstractMetaType::NativePointerPattern; +} + +AbstractMetaType::TypeUsagePattern AbstractMetaType::determineUsagePattern() const +{ + return d->determineUsagePattern(); +} + +void AbstractMetaType::decideUsagePattern() +{ + TypeUsagePattern pattern = determineUsagePattern(); + if (d->m_typeEntry->isObject() && indirections() == 1 + && d->m_referenceType == LValueReference && isConstant()) { + // const-references to pointers can be passed as pointers + setReferenceType(NoReference); + setConstant(false); + pattern = ObjectPattern; + } + setTypeUsagePattern(pattern); + Q_ASSERT(pattern != ContainerPattern || !d->m_instantiations.isEmpty()); +} + +bool AbstractMetaTypeData::hasTemplateChildren() const +{ + QStack<AbstractMetaType> children; + children << m_instantiations; + + // Recursively iterate over the children / descendants of the type, to check if any of them + // corresponds to a template argument type. + while (!children.isEmpty()) { + AbstractMetaType child = children.pop(); + if (child.typeEntry()->isTemplateArgument()) + return true; + children << child.instantiations(); + } + return false; +} + +bool AbstractMetaType::hasTemplateChildren() const +{ + return d->hasTemplateChildren(); +} + +static inline QString formatArraySize(int e) +{ + QString result; + result += u'['; + if (e >= 0) + result += QString::number(e); + result += u']'; + return result; +} + +// Return the number of template parameters; remove the default +// non template type parameter of std::span from the signature. +static qsizetype stripDefaultTemplateArgs(const TypeEntryCPtr &te, + const AbstractMetaTypeList &instantiations) +{ + static const char16_t dynamicExtent64[] = u"18446744073709551615"; // size_t(-1) + static const char16_t dynamicExtent32[] = u"4294967295"; + + qsizetype result = instantiations.size(); + if (result == 0 || !te->isContainer()) + return result; + auto cte = std::static_pointer_cast<const ContainerTypeEntry>(te); + if (cte->containerKind() != ContainerTypeEntry::SpanContainer) + return result; + const auto lastTe = instantiations.constLast().typeEntry(); + if (lastTe->type() == TypeEntry::ConstantValueType) { + const QString &name = lastTe->name(); + if (name == dynamicExtent64 || name == dynamicExtent32) + --result; + } + return result; +} + +QString AbstractMetaTypeData::formatSignature(bool minimal) const +{ + QString result; + if (m_constant) + result += u"const "_s; + if (m_volatile) + result += u"volatile "_s; + if (m_pattern == AbstractMetaType::ArrayPattern) { + // Build nested array dimensions a[2][3] in correct order + result += m_arrayElementType->minimalSignature(); + const int arrayPos = result.indexOf(u'['); + if (arrayPos != -1) + result.insert(arrayPos, formatArraySize(m_arrayElementCount)); + else + result.append(formatArraySize(m_arrayElementCount)); + } else { + result += m_typeEntry->qualifiedCppName(); + } + if (!m_instantiations.isEmpty()) { + result += u'<'; + if (minimal) + result += u' '; + const auto size = stripDefaultTemplateArgs(m_typeEntry, m_instantiations); + for (qsizetype i = 0; i < size; ++i) { + if (i > 0) + result += u','; + result += m_instantiations.at(i).minimalSignature(); + } + result += u'>'; + } + + if (!minimal && (!m_indirections.isEmpty() || m_referenceType != NoReference)) + result += u' '; + for (Indirection i : m_indirections) + result += TypeInfo::indirectionKeyword(i); + switch (m_referenceType) { + case NoReference: + break; + case LValueReference: + result += u'&'; + break; + case RValueReference: + result += u"&&"_s; + break; + } + return result; +} + +QString AbstractMetaType::formatSignature(bool minimal) const +{ + return d->formatSignature(minimal); +} + +QString AbstractMetaTypeData::formatPythonSignature() const +{ + /* + * This is a version of the above, more suitable for Python. + * We avoid extra keywords that are not needed in Python. + * We prepend the package name, unless it is a primitive type. + * + * Primitive types like 'int', 'char' etc.: + * When we have a primitive with an indirection, we use that '*' + * character for later postprocessing, since those indirections + * need to be modified into a result tuple. + * Smart pointer instantiations: Drop the package + */ + QString result; + if (m_pattern == AbstractMetaType::NativePointerAsArrayPattern) + result += u"array "_s; + // We no longer use the "const" qualifier for heuristics. Instead, + // NativePointerAsArrayPattern indicates when we have <array> in XML. + // if (m_typeEntry->isPrimitive() && isConstant()) + // result += QLatin1String("const "); + if (!m_typeEntry->isPrimitive() && !m_typeEntry->isSmartPointer()) { + const QString package = m_typeEntry->targetLangPackage(); + if (!package.isEmpty()) + result += package + u'.'; + } + if (m_pattern == AbstractMetaType::ArrayPattern) { + // Build nested array dimensions a[2][3] in correct order + result += m_arrayElementType->formatPythonSignature(); + const int arrayPos = result.indexOf(u'['); + if (arrayPos != -1) + result.insert(arrayPos, formatArraySize(m_arrayElementCount)); + else + result.append(formatArraySize(m_arrayElementCount)); + } else { + result += m_typeEntry->targetLangName(); + } + if (!m_instantiations.isEmpty()) { + result += u'['; + for (qsizetype i = 0, size = m_instantiations.size(); i < size; ++i) { + if (i > 0) + result += u", "_s; + result += m_instantiations.at(i).formatPythonSignature(); + } + result += u']'; + } + if (m_typeEntry->isPrimitive()) + for (Indirection i : m_indirections) + result += TypeInfo::indirectionKeyword(i); + // If it is a flags type, we replace it with the full name of the enum: + // "PySide6.QtCore.Qt.ItemFlag" instead of "PySide6.QtCore.QFlags<Qt.ItemFlag>" + if (m_typeEntry->isFlags()) { + const auto fte = std::static_pointer_cast<const FlagsTypeEntry>(m_typeEntry); + result = fte->originator()->qualifiedTargetLangName(); + } + result.replace(u"::"_s, u"."_s); + return result; +} + +QString AbstractMetaType::formatPythonSignature() const +{ + return d->formatPythonSignature(); +} + +bool AbstractMetaType::isCppPrimitive() const +{ + return d->m_pattern == PrimitivePattern && ::isCppPrimitive(d->m_typeEntry); +} + +bool AbstractMetaType::isConstant() const +{ + return d->m_constant; +} + +void AbstractMetaType::setConstant(bool constant) +{ + if (d->m_constant != constant) { + d->m_constant = constant; + d->m_signaturesDirty = true; + } +} + +bool AbstractMetaType::isVolatile() const +{ + return d->m_volatile; +} + +void AbstractMetaType::setVolatile(bool v) +{ + if (d->m_volatile != v) { + d->m_volatile = v; + d->m_signaturesDirty = true;\ + } +} + +static bool equalsCPtr(const AbstractMetaTypeCPtr &t1, const AbstractMetaTypeCPtr &t2) +{ + if (bool(t1) != bool(t2)) + return false; + return !t1 || *t1 == *t2; +} + +bool AbstractMetaTypeData::isEquivalent(const AbstractMetaTypeData &rhs) const +{ + if (m_typeEntry != rhs.m_typeEntry + || m_indirections != rhs.m_indirections + || m_arrayElementCount != rhs.m_arrayElementCount) { + return false; + } + + if (!equalsCPtr(m_arrayElementType, rhs.m_arrayElementType)) + return false; + + if (!equalsCPtr(m_viewOn, rhs.m_viewOn)) + return false; + + if (m_instantiations != rhs.m_instantiations) + return false; + return true; +} + +bool AbstractMetaTypeData::equals(const AbstractMetaTypeData &rhs) const +{ + return m_constant == rhs.m_constant && m_volatile == rhs.m_volatile + && m_referenceType == rhs.m_referenceType && isEquivalent(rhs); +} + +bool comparesEqual(const AbstractMetaType &lhs, const AbstractMetaType &rhs) noexcept +{ + return lhs.d->equals(*rhs.d); +} + +bool AbstractMetaType::isEquivalent(const AbstractMetaType &rhs) const +{ + return d->isEquivalent(*rhs.d); +} + +const AbstractMetaType *AbstractMetaType::viewOn() const +{ + return d->m_viewOn.get(); +} + +void AbstractMetaType::setViewOn(const AbstractMetaType &v) +{ + if (!d->m_viewOn || *d->m_viewOn != v) + d->m_viewOn.reset(new AbstractMetaType(v)); +} + +AbstractMetaType AbstractMetaType::createVoid() +{ + static QScopedPointer<AbstractMetaType> metaType; + if (metaType.isNull()) { + static TypeEntryCPtr voidTypeEntry = TypeDatabase::instance()->findType(u"void"_s); + Q_ASSERT(voidTypeEntry); + metaType.reset(new AbstractMetaType(voidTypeEntry)); + metaType->decideUsagePattern(); + } + return *metaType.data(); +} + +void AbstractMetaType::dereference(QString *type) +{ + type->prepend(u"(*"_s); + type->append(u')'); +} + +QString AbstractMetaType::dereferencePrefix(qsizetype n) +{ + const QChar c = n > 0 ? u'*' : u'&'; + return QString(qAbs(n), c); +} + +void AbstractMetaType::applyDereference(QString *type, qsizetype n) +{ + if (n == 0) + return; + + type->prepend(dereferencePrefix(n)); + type->prepend(u'('); + type->append(u')'); +} + +bool AbstractMetaType::stripDereference(QString *type) +{ + if (type->startsWith(u"(*") && type->endsWith(u')')) { + type->chop(1); + type->remove(0, 2); + *type = type->trimmed(); + return true; + } + if (type->startsWith(u'*')) { + type->remove(0, 1); + *type = type->trimmed(); + return true; + } + return false; +} + +// Query functions for generators +bool AbstractMetaType::isObjectType() const +{ + return d->m_typeEntry->isObject(); +} + +bool AbstractMetaType::isUniquePointer() const +{ + return isSmartPointer() && d->m_typeEntry->isUniquePointer(); +} + +bool AbstractMetaType::isPointer() const +{ + return !d->m_indirections.isEmpty() + || isNativePointer() || isValuePointer(); +} + +bool AbstractMetaType::isPointerToConst() const +{ + return d->m_constant && !d->m_indirections.isEmpty() + && d->m_indirections.constLast() != Indirection::ConstPointer; +} + +bool AbstractMetaType::isCString() const +{ + return isNativePointer() + && d->m_indirections.size() == 1 + && name() == u"char"; +} + +bool AbstractMetaType::isVoidPointer() const +{ + return isNativePointer() + && d->m_indirections.size() == 1 + && name() == u"void"; +} + +bool AbstractMetaType::isUserPrimitive() const +{ + return d->m_indirections.isEmpty() && ::isUserPrimitive(d->m_typeEntry); +} + +bool AbstractMetaType::isObjectTypeUsedAsValueType() const +{ + return d->m_typeEntry->isObject() && d->m_referenceType == NoReference + && d->m_indirections.isEmpty(); +} + +bool AbstractMetaType::isWrapperType() const +{ + return d->m_typeEntry->isWrapperType(); +} + +bool AbstractMetaType::isPointerToWrapperType() const +{ + return (isObjectType() && d->m_indirections.size() == 1) || isValuePointer(); +} + +bool AbstractMetaType::isWrapperPassedByReference() const +{ + return d->m_referenceType == LValueReference && isWrapperType() + && !isPointer(); +} + +bool AbstractMetaType::isCppIntegralPrimitive() const +{ + return ::isCppIntegralPrimitive(d->m_typeEntry); +} + +bool AbstractMetaType::isExtendedCppPrimitive() const +{ + if (isCString() || isVoidPointer()) + return true; + if (!d->m_indirections.isEmpty()) + return false; + return ::isExtendedCppPrimitive(d->m_typeEntry); +} + +bool AbstractMetaType::isValueTypeWithCopyConstructorOnly() const +{ + bool result = false; + if (d->m_typeEntry->isComplex()) { + const auto cte = std::static_pointer_cast<const ComplexTypeEntry>(d->m_typeEntry); + result = cte->isValueTypeWithCopyConstructorOnly(); + } + return result; +} + +bool AbstractMetaType::valueTypeWithCopyConstructorOnlyPassed() const +{ + return (passByValue() || passByConstRef()) + && isValueTypeWithCopyConstructorOnly(); +} + +using AbstractMetaTypeCache = QHash<QString, AbstractMetaType>; + +Q_GLOBAL_STATIC(AbstractMetaTypeCache, metaTypeFromStringCache) + +std::optional<AbstractMetaType> +AbstractMetaType::fromString(const QString &typeSignatureIn, QString *errorMessage) +{ + auto &cache = *metaTypeFromStringCache(); + auto it = cache.find(typeSignatureIn); + if (it != cache.end()) + return it.value(); + + QString typeSignature = typeSignatureIn.trimmed(); + if (typeSignature.startsWith(u"::")) + typeSignature.remove(0, 2); + + it = cache.find(typeSignature); + if (it == cache.end()) { + auto metaType = + AbstractMetaBuilder::translateType(typeSignature, nullptr, {}, errorMessage); + if (Q_UNLIKELY(!metaType.has_value())) { + if (errorMessage) + errorMessage->prepend(msgCannotBuildMetaType(typeSignature)); + return {}; + } + if (typeSignature != typeSignatureIn) + cache.insert(typeSignatureIn, metaType.value()); + it = cache.insert(typeSignature, metaType.value()); + } + return it.value(); +} + +AbstractMetaType AbstractMetaType::fromTypeEntry(const TypeEntryCPtr &typeEntry) +{ + QString typeName = typeEntry->qualifiedCppName(); + if (typeName.startsWith(u"::")) + typeName.remove(0, 2); + auto &cache = *metaTypeFromStringCache(); + auto it = cache.find(typeName); + if (it != cache.end()) + return it.value(); + AbstractMetaType metaType = AbstractMetaType(typeEntry).plainType(); + cache.insert(typeName, metaType); + return metaType; +} + +AbstractMetaType AbstractMetaType::fromAbstractMetaClass(const AbstractMetaClassCPtr &metaClass) +{ + return fromTypeEntry(metaClass->typeEntry()); +} + +template <class Predicate> // Predicate(containerTypeEntry, signature) +bool AbstractMetaTypeData::generateOpaqueContainer(Predicate pred) const +{ + // Allow for passing containers by pointer as well. + if (!m_typeEntry->isContainer()) + return false; + if (m_indirections.size() > 1) + return false; + auto containerTypeEntry = std::static_pointer_cast<const ContainerTypeEntry>(m_typeEntry); + auto kind = containerTypeEntry->containerKind(); + if (kind != ContainerTypeEntry::ListContainer && kind != ContainerTypeEntry::SpanContainer) + return false; + + const auto &firstInstantiation = m_instantiations.constFirst(); + if (firstInstantiation.referenceType() != NoReference) + return false; + switch (firstInstantiation.typeEntry()->type()) { + case TypeEntry::PrimitiveType: + case TypeEntry::FlagsType: + case TypeEntry::EnumType: + case TypeEntry::BasicValueType: + case TypeEntry::ObjectType: + case TypeEntry::CustomType: + break; + default: + return false; + } + + return pred(containerTypeEntry, instantiationCppSignatures()); +} + +// Simple predicate for checking whether an opaque container should be generated +static bool opaqueContainerPredicate(const ContainerTypeEntryCPtr &t, + const QStringList &instantiations) +{ + return t->generateOpaqueContainer(instantiations); +} + +bool AbstractMetaType::generateOpaqueContainer() const +{ + return d->generateOpaqueContainer(opaqueContainerPredicate); +} + +// Helper for determining whether a function should return an opaque container, +// that is, the function return type is modified accordingly +// (cf AbstractMetaFunction::generateOpaqueContainerReturn()) +bool AbstractMetaType::generateOpaqueContainerForGetter(const QString &modifiedType) const +{ + auto predicate = [&modifiedType](const ContainerTypeEntryCPtr &t, + const QStringList &instantiations) { + return t->opaqueContainerName(instantiations) == modifiedType; + }; + return d->generateOpaqueContainer(predicate); +} + +#ifndef QT_NO_DEBUG_STREAM +void AbstractMetaType::formatDebug(QDebug &debug) const +{ + debug << '"' << name() << '"'; + if (debug.verbosity() > 2 && !isVoid()) { + auto te = typeEntry(); + debug << ", typeEntry="; + if (debug.verbosity() > 3) + debug << te; + else + debug << "(\"" << te->qualifiedCppName() << "\", " << te->type() << ')'; + debug << ", signature=\"" << cppSignature() << "\", pattern=" + << typeUsagePattern(); + const auto indirections = indirectionsV(); + if (!indirections.isEmpty()) { + debug << ", indirections="; + for (auto i : indirections) + debug << ' ' << TypeInfo::indirectionKeyword(i); + } + if (referenceType() != NoReference) + debug << ", reftype=" << referenceType(); + if (isConstant()) + debug << ", [const]"; + if (isVolatile()) + debug << ", [volatile]"; + if (isArray()) { + debug << ", array of \"" << arrayElementType()->cppSignature() + << "\", arrayElementCount=" << arrayElementCount(); + } + const auto &instantiations = this->instantiations(); + if (const auto instantiationsSize = instantiations.size()) { + debug << ", instantiations[" << instantiationsSize << "]=<"; + for (qsizetype i = 0; i < instantiationsSize; ++i) { + if (i) + debug << ", "; + instantiations.at(i).formatDebug(debug); + } + } + debug << '>'; + if (viewOn()) + debug << ", views " << viewOn()->name(); + } +} + +QDebug operator<<(QDebug d, const AbstractMetaType &at) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AbstractMetaType("; + at.formatDebug(d); + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const AbstractMetaType *at) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + if (at) + d << *at; + else + d << "AbstractMetaType(0)"; + return d; +} + +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/abstractmetatype.h b/sources/shiboken6/ApiExtractor/abstractmetatype.h new file mode 100644 index 000000000..8a1ecdf20 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/abstractmetatype.h @@ -0,0 +1,276 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETATYPE_H +#define ABSTRACTMETATYPE_H + +#include "abstractmetalang_enums.h" +#include "abstractmetalang_typedefs.h" +#include "parser/codemodel_enums.h" +#include "typedatabase_typedefs.h" + +#include <QtCore/QtCompare> +#include <QtCore/qobjectdefs.h> +#include <QtCore/QHashFunctions> +#include <QtCore/QSharedDataPointer> +#include <QtCore/QList> +#include <QtCore/QSet> + +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class AbstractMetaTypeData; +class TypeEntry; + +class AbstractMetaType +{ + Q_GADGET +public: + using Indirections = QList<Indirection>; + + enum TypeUsagePattern { + PrimitivePattern, + FlagsPattern, + EnumPattern, + ValuePattern, + ObjectPattern, + ValuePointerPattern, + NativePointerPattern, + NativePointerAsArrayPattern, // "int*" as "int[]" + ContainerPattern, + SmartPointerPattern, + VarargsPattern, + ArrayPattern, + VoidPattern, // Plain "void", no "void *" or similar. + TemplateArgument, // 'T' in std::array<T,2> + NonTypeTemplateArgument // '2' in in std::array<T,2> + }; + Q_ENUM(TypeUsagePattern) + + AbstractMetaType(); + explicit AbstractMetaType(const TypeEntryCPtr &t); + AbstractMetaType(const AbstractMetaType &); + AbstractMetaType &operator=(const AbstractMetaType &); + AbstractMetaType(AbstractMetaType &&) noexcept; + AbstractMetaType &operator=(AbstractMetaType &&) noexcept; + ~AbstractMetaType(); + + QString package() const; + QString name() const; + QString fullName() const; + + void setTypeUsagePattern(TypeUsagePattern pattern); + TypeUsagePattern typeUsagePattern() const; + + // true when use pattern is container + bool hasInstantiations() const; + + const AbstractMetaTypeList &instantiations() const; + void addInstantiation(const AbstractMetaType &inst); + void setInstantiations(const AbstractMetaTypeList &insts); + QStringList instantiationCppSignatures() const; + + QString minimalSignature() const { return formatSignature(true); } + + // returns true if the typs is used as a non complex primitive, no & or *'s + bool isPrimitive() const { return typeUsagePattern() == PrimitivePattern; } + + bool isCppPrimitive() const; + + // returns true if the type is used as an enum + bool isEnum() const { return typeUsagePattern() == EnumPattern; } + + // returns true if the type is used as an object, e.g. Xxx * + bool isObject() const { return typeUsagePattern() == ObjectPattern; } + + // returns true if the type is indicated an object by the TypeEntry + bool isObjectType() const; + + // returns true if the type is used as an array, e.g. Xxx[42] + bool isArray() const { return typeUsagePattern() == ArrayPattern; } + + // returns true if the type is used as a value type (X or const X &) + bool isValue() const { return typeUsagePattern() == ValuePattern; } + + bool isValuePointer() const { return typeUsagePattern() == ValuePointerPattern; } + + // returns true for more complex types... + bool isNativePointer() const { return typeUsagePattern() == NativePointerPattern; } + + // return true if the type was originally a varargs + bool isVarargs() const { return typeUsagePattern() == VarargsPattern; } + + // returns true if the type was used as a container + bool isContainer() const { return typeUsagePattern() == ContainerPattern; } + + // returns true if the type was used as a smart pointer + bool isSmartPointer() const { return typeUsagePattern() == SmartPointerPattern; } + bool isUniquePointer() const; + + // returns true if the type was used as a flag + bool isFlags() const { return typeUsagePattern() == FlagsPattern; } + + bool isVoid() const { return typeUsagePattern() == VoidPattern; } + + bool isConstant() const; + void setConstant(bool constant); + + bool isVolatile() const; + void setVolatile(bool v); + + bool passByConstRef() const; + bool passByValue() const; + bool useStdMove() const; + + ReferenceType referenceType() const; + void setReferenceType(ReferenceType ref); + + int actualIndirections() const; + + Indirections indirectionsV() const; + void setIndirectionsV(const Indirections &i); + void clearIndirections(); + + // "Legacy"? + int indirections() const; + void setIndirections(int indirections); + void addIndirection(Indirection i = Indirection::Pointer); + + void setArrayElementCount(int n); + int arrayElementCount() const; + + const AbstractMetaType *arrayElementType() const; + void setArrayElementType(const AbstractMetaType &t); + + AbstractMetaTypeList nestedArrayTypes() const; + + /// Strip const/indirections/reference from the type + AbstractMetaType plainType() const; + + QString cppSignature() const; + + QString pythonSignature() const; + + bool applyArrayModification(QString *errorMessage); + + TypeEntryCPtr typeEntry() const; + void setTypeEntry(const TypeEntryCPtr &type); + + void setOriginalTypeDescription(const QString &otd); + QString originalTypeDescription() const; + + void setOriginalTemplateType(const AbstractMetaType &type); + const AbstractMetaType *originalTemplateType() const; + + AbstractMetaType getSmartPointerInnerType() const; + + QString getSmartPointerInnerTypeName() const; + + /// Decides and sets the proper usage patter for the current meta type. + void decideUsagePattern(); + + bool hasTemplateChildren() const; + + /// Is equivalent from the POV of argument passing (differ by const ref) + bool isEquivalent(const AbstractMetaType &rhs) const; + + // View on: Type to use for function argument conversion, fex + // std::string_view -> std::string for foo(std::string_view); + // cf TypeEntry::viewOn() + const AbstractMetaType *viewOn() const; + void setViewOn(const AbstractMetaType &v); + + static AbstractMetaType createVoid(); + + /// Builds an AbstractMetaType object from a QString. + /// Returns nullopt if no type could be built from the string. + /// \param typeSignature The string describing the type to be built. + /// \return A new AbstractMetaType object or nullopt in case of failure. + static std::optional<AbstractMetaType> + fromString(const QString &typeSignatureIn, QString *errorMessage = nullptr); + /// Creates an AbstractMetaType object from a TypeEntry. + static AbstractMetaType fromTypeEntry(const TypeEntryCPtr &typeEntry); + /// Creates an AbstractMetaType object from an AbstractMetaClass. + static AbstractMetaType fromAbstractMetaClass(const AbstractMetaClassCPtr &metaClass); + + static void dereference(QString *type); // "foo" -> "(*foo)" + /// Apply the result of shouldDereferenceArgument() + static QString dereferencePrefix(qsizetype n); // Return the prefix **/& as as required + static void applyDereference(QString *type, qsizetype n); + static bool stripDereference(QString *type); // "(*foo)" -> "foo" + + // Query functions for generators + /// Check if type is a pointer. + bool isPointer() const; + /// Helper for field setters: Check for "const QWidget *" (settable field), + /// but not "int *const" (read-only field). + bool isPointerToConst() const; + /// Returns true if the type is a C string (const char *). + bool isCString() const; + /// Returns true if the type is a void pointer. + bool isVoidPointer() const; + /// Returns true if the type is a primitive but not a C++ primitive. + bool isUserPrimitive() const; + /// Returns true if it is an Object Type used as a value. + bool isObjectTypeUsedAsValueType() const; + /// Returns true if the type passed has a Python wrapper for it. + /// Although namespace has a Python wrapper, it's not considered a type. + bool isWrapperType() const; + /// Checks if the type is an Object/QObject or pointer to Value Type. + /// In other words, tells if the type is "T*" and T has a Python wrapper. + bool isPointerToWrapperType() const; + /// Wrapper type passed by reference + bool isWrapperPassedByReference() const; + /// Returns true if the type is a C++ integral primitive, + /// i.e. bool, char, int, long, and their unsigned counterparts. + bool isCppIntegralPrimitive() const; + /// Returns true if the type is an extended C++ primitive, a void*, + /// a const char*, or a std::string (cf isCppPrimitive()). + bool isExtendedCppPrimitive() const; + /// Returns whether the underlying type is a value type with copy constructor only + bool isValueTypeWithCopyConstructorOnly() const; + /// Returns whether the type (function argument) is a value type with + /// copy constructor only is passed as value or const-ref and thus + /// no default value can be constructed. + bool valueTypeWithCopyConstructorOnlyPassed() const; + /// Returns whether to generate an opaque container for the type + bool generateOpaqueContainer() const; + /// Returns whether to generate an opaque container for a getter + bool generateOpaqueContainerForGetter(const QString &modifiedType) const; + + /// Types for which libshiboken has built-in primitive converters + static const QSet<QString> &cppFloatTypes(); + static const QSet<QString> &cppSignedCharTypes(); + static const QSet<QString> &cppUnsignedCharTypes(); + static const QSet<QString> &cppCharTypes(); + static const QSet<QString> &cppSignedIntTypes(); + static const QSet<QString> &cppUnsignedIntTypes(); + static const QSet<QString> &cppIntegralTypes(); + static const QSet<QString> &cppPrimitiveTypes(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &debug) const; +#endif + +private: + friend size_t qHash(const AbstractMetaType &t, size_t seed = 0) noexcept + { return qHash(t.typeEntry().get(), seed); } + friend bool comparesEqual(const AbstractMetaType &lhs, + const AbstractMetaType &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(AbstractMetaType) + + friend class AbstractMetaTypeData; + QSharedDataPointer<AbstractMetaTypeData> d; + + TypeUsagePattern determineUsagePattern() const; + QString formatSignature(bool minimal) const; + QString formatPythonSignature() const; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const AbstractMetaType &at); +QDebug operator<<(QDebug d, const AbstractMetaType *at); +#endif + +#endif // ABSTRACTMETALANG_H diff --git a/sources/shiboken6/ApiExtractor/addedfunction.cpp b/sources/shiboken6/ApiExtractor/addedfunction.cpp new file mode 100644 index 000000000..9d95b734c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/addedfunction.cpp @@ -0,0 +1,216 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "addedfunction.h" +#include "addedfunction_p.h" +#include "typeparser.h" + +#include <QtCore/QDebug> + +using namespace Qt::StringLiterals; + +constexpr auto callOperator = "operator()"_L1; + +// Helpers to split a parameter list of <add-function>, <declare-function> +// (@ denoting names), like +// "void foo(QList<X,Y> &@list@ = QList<X,Y>{1,2}, int @b@=5, ...)" +namespace AddedFunctionParser { + +QDebug operator<<(QDebug d, const Argument &a) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "Argument(type=\"" << a.type << '"'; + if (!a.name.isEmpty()) + d << ", name=\"" << a.name << '"'; + if (!a.defaultValue.isEmpty()) + d << ", defaultValue=\"" << a.defaultValue << '"'; + d << ')'; + return d; +} + +// Helper for finding the end of a function parameter, observing +// nested template parameters or lists. +static qsizetype parameterTokenEnd(qsizetype startPos, QStringView paramString) +{ + const auto end = paramString.size(); + int nestingLevel = 0; + for (qsizetype p = startPos; p < end; ++p) { + switch (paramString.at(p).toLatin1()) { + case ',': + if (nestingLevel == 0) + return p; + break; + case '<': // templates + case '{': // initializer lists of default values + case '(': // initialization, function pointers + case '[': // array dimensions + ++nestingLevel; + break; + case '>': + case '}': + case ')': + case ']': + --nestingLevel; + break; + } + } + return end; +} + +// Split a function parameter list into string tokens containing one +// parameters (including default value, etc). +static QList<QStringView> splitParameterTokens(QStringView paramString) +{ + QList<QStringView> result; + qsizetype startPos = 0; + for ( ; startPos < paramString.size(); ) { + const auto end = parameterTokenEnd(startPos, paramString); + result.append(paramString.mid(startPos, end - startPos).trimmed()); + startPos = end + 1; + } + return result; +} + +// Split a function parameter list +Arguments splitParameters(QStringView paramString, QString *errorMessage) +{ + Arguments result; + const QList<QStringView> tokens = splitParameterTokens(paramString); + + for (const auto &t : tokens) { + Argument argument; + // Check defaultValue, "int @b@=5" + const auto equalPos = t.lastIndexOf(u'='); + if (equalPos != -1) { + const int defaultValuePos = equalPos + 1; + argument.defaultValue = + t.mid(defaultValuePos, t.size() - defaultValuePos).trimmed().toString(); + } + QString typeString = (equalPos != -1 ? t.left(equalPos) : t).trimmed().toString(); + // Check @name@ + const auto atPos = typeString.indexOf(u'@'); + if (atPos != -1) { + const int namePos = atPos + 1; + const int nameEndPos = typeString.indexOf(u'@', namePos); + if (nameEndPos == -1) { + if (errorMessage != nullptr) { + *errorMessage = u"Mismatched @ in \""_s + + paramString.toString() + u'"'; + } + return {}; + } + argument.name = typeString.mid(namePos, nameEndPos - namePos).trimmed(); + typeString.remove(atPos, nameEndPos - atPos + 1); + } + argument.type = typeString.trimmed(); + result.append(argument); + } + + return result; +} + +} // namespace AddedFunctionParser + +AddedFunction::AddedFunction(const QString &name, const QList<Argument> &arguments, + const TypeInfo &returnType) : + m_name(name), + m_arguments(arguments), + m_returnType(returnType) +{ +} + +AddedFunction::AddedFunctionPtr + AddedFunction::createAddedFunction(const QString &signatureIn, const QString &returnTypeIn, + QString *errorMessage) + +{ + errorMessage->clear(); + + QList<Argument> arguments; + const TypeInfo returnType = returnTypeIn.isEmpty() + ? TypeInfo::voidType() + : TypeParser::parse(returnTypeIn, errorMessage); + if (!errorMessage->isEmpty()) + return {}; + + QStringView signature = QStringView{signatureIn}.trimmed(); + + // Skip past "operator()(...)" + const auto parenSearchStartPos = signature.startsWith(callOperator) + ? callOperator.size() : 0; + const auto openParenPos = signature.indexOf(u'(', parenSearchStartPos); + if (openParenPos < 0) { + return AddedFunctionPtr(new AddedFunction(signature.toString(), + arguments, returnType)); + } + + const QString name = signature.left(openParenPos).trimmed().toString(); + const auto closingParenPos = signature.lastIndexOf(u')'); + if (closingParenPos < 0) { + *errorMessage = u"Missing closing parenthesis"_s; + return {}; + } + + // Check for "foo() const" + bool isConst = false; + const auto signatureLength = signature.length(); + const auto qualifierLength = signatureLength - closingParenPos - 1; + if (qualifierLength >= 5 + && signature.right(qualifierLength).contains(u"const")) { + isConst = true; + } + + const auto paramString = signature.mid(openParenPos + 1, closingParenPos - openParenPos - 1); + const auto params = AddedFunctionParser::splitParameters(paramString, errorMessage); + if (params.isEmpty() && !errorMessage->isEmpty()) + return {}; + for (const auto &p : params) { + TypeInfo type = p.type == u"..." + ? TypeInfo::varArgsType() : TypeParser::parse(p.type, errorMessage); + if (!errorMessage->isEmpty()) { + errorMessage->prepend(u"Unable to parse added function "_s + signatureIn + + u": "_s); + return {}; + } + arguments.append({type, p.name, p.defaultValue}); + } + + auto result = std::make_shared<AddedFunction>(name, arguments, returnType); + result->setConstant(isConst); + return result; +} + +QDebug operator<<(QDebug d, const AddedFunction::Argument &a) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "Argument("; + d << a.typeInfo; + if (!a.name.isEmpty()) + d << ' ' << a.name; + if (!a.defaultValue.isEmpty()) + d << " = " << a.defaultValue; + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const AddedFunction &af) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "AddedFunction("; + if (af.access() == AddedFunction::Protected) + d << "protected"; + if (af.isStatic()) + d << " static"; + d << af.returnType() << ' ' << af.name() << '(' << af.arguments() << ')'; + if (af.isConstant()) + d << " const"; + if (af.isDeclaration()) + d << " [declaration]"; + return d; +} diff --git a/sources/shiboken6/ApiExtractor/addedfunction.h b/sources/shiboken6/ApiExtractor/addedfunction.h new file mode 100644 index 000000000..b8d189b7a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/addedfunction.h @@ -0,0 +1,113 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ADDEDFUNCTION_H +#define ADDEDFUNCTION_H + +#include "modifications.h" +#include "parser/typeinfo.h" + +#include <QtCore/QList> +#include <QtCore/QString> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +/// \internal +/// Struct used to store information about functions added by the typesystem. +/// This info will be used later to create a fake AbstractMetaFunction which +/// will be inserted into the right AbstractMetaClass. +struct AddedFunction +{ + using AddedFunctionPtr = std::shared_ptr<AddedFunction>; + + /// Function access types. + enum Access { + Protected = 0x1, + Public = 0x2 + }; + + struct Argument + { + TypeInfo typeInfo; + QString name; + QString defaultValue; + }; + + /// Creates a new AddedFunction with a signature and a return type. + explicit AddedFunction(const QString &name, const QList<Argument> &arguments, + const TypeInfo &returnType); + + static AddedFunctionPtr createAddedFunction(const QString &signatureIn, + const QString &returnTypeIn, + QString *errorMessage); + + AddedFunction() = default; + + /// Returns the function name. + QString name() const { return m_name; } + + /// Set the function access type. + void setAccess(Access access) { m_access = access; } + + /// Returns the function access type. + Access access() const { return m_access; } + + /// Returns the function return type. + const TypeInfo &returnType() const { return m_returnType; } + + /// Returns a list of argument type infos. + const QList<Argument> &arguments() const { return m_arguments; } + + /// Returns true if this is a constant method. + bool isConstant() const { return m_isConst; } + void setConstant(bool c) { m_isConst = c; }; + + /// Set this method static. + void setStatic(bool value) { m_isStatic = value; } + + /// Set this method as a classmethod. + void setClassMethod(bool value) { m_isClassMethod = value; } + + /// Returns true if this is a static method. + bool isStatic() const { return m_isStatic; } + + /// Returns true if this is a class method. + bool isClassMethod() const { return m_isClassMethod; } + + bool isDeclaration() const { return m_isDeclaration; } // <declare-function> + void setDeclaration(bool value) { m_isDeclaration = value; } + + bool isPythonOverride() const { return m_isPythonOverride; } + void setPythonOverride(bool o) { m_isPythonOverride = o; } + + const FunctionModificationList &modifications() const { return m_modifications; } + FunctionModificationList &modifications() { return m_modifications; } + + const DocModificationList &docModifications() const { return m_docModifications; } + DocModificationList &docModifications() { return m_docModifications; } + void addDocModification(const DocModification &m) { m_docModifications.append(m); } + + QString targetLangPackage() const { return m_targetLangPackage; } + void setTargetLangPackage(const QString &p) { m_targetLangPackage = p; } + +private: + QString m_name; + QList<Argument> m_arguments; + TypeInfo m_returnType; + FunctionModificationList m_modifications; + DocModificationList m_docModifications; + QString m_targetLangPackage; + Access m_access = Public; + bool m_isConst = false; + bool m_isClassMethod = false; + bool m_isStatic = false; + bool m_isDeclaration = false; + bool m_isPythonOverride = false; +}; + +QDebug operator<<(QDebug d, const AddedFunction::Argument &a); +QDebug operator<<(QDebug d, const AddedFunction &af); + +#endif // ADDEDFUNCTION_H diff --git a/sources/shiboken6/ApiExtractor/addedfunction_p.h b/sources/shiboken6/ApiExtractor/addedfunction_p.h new file mode 100644 index 000000000..40b69a5df --- /dev/null +++ b/sources/shiboken6/ApiExtractor/addedfunction_p.h @@ -0,0 +1,45 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ADDEDFUNCTION_P_H +#define ADDEDFUNCTION_P_H + +#include <QtCore/QtCompare> +#include <QtCore/QList> +#include <QtCore/QString> +#include <QtCore/QStringView> + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +// Helpers to split a parameter list of <add-function>, <declare-function> +// in a separate header for testing purposes + +namespace AddedFunctionParser { + +struct Argument +{ + QString type; + QString name; + QString defaultValue; + + friend bool comparesEqual(const Argument &lhs, const Argument &rhs) noexcept + { + return lhs.type == rhs.type && lhs.name == rhs.name + && lhs.defaultValue == rhs.defaultValue; + } + Q_DECLARE_EQUALITY_COMPARABLE(Argument) +}; + +using Arguments = QList<Argument>; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const Argument &a); +#endif + +Arguments splitParameters(QStringView paramString, QString *errorMessage = nullptr); + +} // namespace AddedFunctionParser + +#endif // MODIFICATIONS_P_H diff --git a/sources/shiboken6/ApiExtractor/anystringview_helpers.cpp b/sources/shiboken6/ApiExtractor/anystringview_helpers.cpp new file mode 100644 index 000000000..35d2d535a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/anystringview_helpers.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "anystringview_helpers.h" + +#include <QtCore/QString> // Must go before QAnyStringView for operator<<(QTextStream,QASV)! +#include <QtCore/QAnyStringView> +#include <QtCore/QDebug> +#include <QtCore/QTextStream> + +#include <cstring> + +QTextStream &operator<<(QTextStream &str, QAnyStringView asv) +{ + asv.visit([&str](auto s) { str << s; }); + return str; +} + +static bool asv_containsImpl(QLatin1StringView v, char c) +{ + return v.contains(uint16_t(c)); +} + +static bool asv_containsImpl(QUtf8StringView v, char c) +{ + return std::strchr(v.data(), c) != nullptr; +} + +static bool asv_containsImpl(QStringView v, char c) +{ + return v.contains(uint16_t(c)); +} + +bool asv_contains(QAnyStringView asv, char needle) +{ + return asv.visit([needle](auto s) { return asv_containsImpl(s, needle); }); +} + +static bool asv_containsImpl(QLatin1StringView v, const char *c) +{ + return v.contains(QLatin1StringView(c)); +} +static bool asv_containsImpl(QUtf8StringView v, const char *c) +{ + return std::strstr(v.data(), c) != nullptr; +} + +static bool asv_containsImpl(QStringView v, const char *c) +{ + return v.contains(QLatin1StringView(c)); +} + +bool asv_contains(QAnyStringView asv, const char *needle) +{ + return asv.visit([needle](auto s) { return asv_containsImpl(s, needle); }); +} diff --git a/sources/shiboken6/ApiExtractor/anystringview_helpers.h b/sources/shiboken6/ApiExtractor/anystringview_helpers.h new file mode 100644 index 000000000..e1e6ab7f0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/anystringview_helpers.h @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ANYSTRINGVIEW_STREAM_H +#define ANYSTRINGVIEW_STREAM_H + +#include <QtCore/QtClassHelperMacros> + +QT_FORWARD_DECLARE_CLASS(QAnyStringView) +QT_FORWARD_DECLARE_CLASS(QTextStream) +QT_FORWARD_DECLARE_CLASS(QDebug) + +QTextStream &operator<<(QTextStream &str, QAnyStringView asv); + +bool asv_contains(QAnyStringView asv, char needle); +bool asv_contains(QAnyStringView asv, const char *needle); + +#endif // ANYSTRINGVIEW_STREAM_H diff --git a/sources/shiboken6/ApiExtractor/apiextractor.cpp b/sources/shiboken6/ApiExtractor/apiextractor.cpp new file mode 100644 index 000000000..786cd0783 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/apiextractor.cpp @@ -0,0 +1,849 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "apiextractor.h" +#include "apiextractorresult.h" +#include "abstractmetaargument.h" +#include "abstractmetabuilder.h" +#include "abstractmetaenum.h" +#include "abstractmetafield.h" +#include "abstractmetafunction.h" +#include "abstractmetalang.h" +#include "codesnip.h" +#include "exception.h" +#include "messages.h" +#include "modifications.h" +#include "optionsparser.h" +#include "reporthandler.h" +#include "typedatabase.h" +#include "customconversion.h" +#include "containertypeentry.h" +#include "primitivetypeentry.h" +#include "smartpointertypeentry.h" +#include "typedefentry.h" +#include "namespacetypeentry.h" +#include "typesystemtypeentry.h" + +#include "qtcompat.h" + +#include <QtCore/QDir> +#include <QtCore/QDebug> +#include <QtCore/QTemporaryFile> + +#include <algorithm> +#include <iostream> +#include <iterator> + +using namespace Qt::StringLiterals; + +struct InstantiationCollectContext +{ + AbstractMetaTypeList instantiatedContainers; + InstantiatedSmartPointers instantiatedSmartPointers; + QStringList instantiatedContainersNames; +}; + +struct ApiExtractorOptions +{ + QString m_typeSystemFileName; + QFileInfoList m_cppFileNames; + HeaderPaths m_includePaths; + QStringList m_clangOptions; + QString m_logDirectory; + LanguageLevel m_languageLevel = LanguageLevel::Default; + bool m_skipDeprecated = false; +}; + +static inline QString languageLevelDescription() +{ + return u"C++ Language level (c++11..c++17, default="_s + + QLatin1StringView(clang::languageLevelOption(clang::emulatedCompilerLanguageLevel())) + + u')'; +} + +QList<OptionDescription> ApiExtractor::options() +{ + return { + {u"use-global-header"_s, + u"Use the global headers in generated code."_s}, + {u"clang-option"_s, + u"Option to be passed to clang"_s}, + {u"clang-options"_s, + u"A comma-separated list of options to be passed to clang"_s}, + {u"skip-deprecated"_s, + u"Skip deprecated functions"_s}, + {u"-F<path>"_s, {} }, + {u"framework-include-paths="_s + OptionsParser::pathSyntax(), + u"Framework include paths used by the C++ parser"_s}, + {u"-isystem<path>"_s, {} }, + {u"system-include-paths="_s + OptionsParser::pathSyntax(), + u"System include paths used by the C++ parser"_s}, + {u"language-level=, -std=<level>"_s, + languageLevelDescription()}, + }; +} + +class ApiExtractorOptionsParser : public OptionsParser +{ +public: + explicit ApiExtractorOptionsParser(ApiExtractorOptions *o) : m_options(o) {} + + bool handleBoolOption(const QString &key, OptionSource source) override; + bool handleOption(const QString &key, const QString &value, + OptionSource source) override; + +private: + void parseIncludePathOption(const QString &value, HeaderType headerType); + void parseIncludePathOption(const QStringList &values, HeaderType headerType); + void setLanguageLevel(const QString &value); + + ApiExtractorOptions *m_options; +}; + +void ApiExtractorOptionsParser::parseIncludePathOption(const QString &value, + HeaderType headerType) +{ + if (value.isEmpty()) + throw Exception(u"Empty value passed to include path option"_s); + const auto path = QFile::encodeName(QDir::cleanPath(value)); + m_options->m_includePaths.append(HeaderPath{path, headerType}); +} + +void ApiExtractorOptionsParser::parseIncludePathOption(const QStringList &values, + HeaderType headerType) +{ + for (const auto &value : values) + parseIncludePathOption(value, headerType); +} + +void ApiExtractorOptionsParser::setLanguageLevel(const QString &value) +{ + const QByteArray languageLevelBA = value.toLatin1(); + const LanguageLevel level = clang::languageLevelFromOption(languageLevelBA.constData()); + if (level == LanguageLevel::Default) + throw Exception(msgInvalidLanguageLevel(value)); + m_options->m_languageLevel = level; +} + +bool ApiExtractorOptionsParser::handleBoolOption(const QString &key, OptionSource source) +{ + static const auto isystemOption = "isystem"_L1; + + switch (source) { + case OptionSource::CommandLine: + case OptionSource::ProjectFile: + if (key == u"use-global-header") { + AbstractMetaBuilder::setUseGlobalHeader(true); + return true; + } + if (key == u"skip-deprecated") { + m_options->m_skipDeprecated = true; + return true; + } + break; + + case OptionSource::CommandLineSingleDash: + if (key.startsWith(u'I')) { // Shorthand path arguments -I/usr/include... + parseIncludePathOption(key.sliced(1), HeaderType::Standard); + return true; + } + if (key.startsWith(u'F')) { + parseIncludePathOption(key.sliced(1), HeaderType::Framework); + return true; + } + if (key.startsWith(isystemOption)) { + parseIncludePathOption(key.sliced(isystemOption.size()), HeaderType::System); + return true; + } + break; + } + return false; +} + +bool ApiExtractorOptionsParser::handleOption(const QString &key, const QString &value, + OptionSource source) +{ + if (source == OptionSource::CommandLineSingleDash) { + if (key == u"std") { + setLanguageLevel(value); + return true; + } + return false; + } + + if (key == u"clang-option") { + m_options->m_clangOptions.append(value); + return true; + } + if (key == u"clang-options") { + m_options->m_clangOptions.append(value.split(u',', Qt::SkipEmptyParts)); + return true; + } + if (key == u"include-paths") { + parseIncludePathOption(value.split(QDir::listSeparator(), Qt::SkipEmptyParts), + HeaderType::Standard); + return true; + } + if (key == u"framework-include-paths") { + parseIncludePathOption(value.split(QDir::listSeparator(), Qt::SkipEmptyParts), + HeaderType::Framework); + return true; + } + if (key == u"system-include-paths") { + parseIncludePathOption(value.split(QDir::listSeparator(), Qt::SkipEmptyParts), + HeaderType::System); + return true; + } + if (key == u"language-level") { + setLanguageLevel(value); + return true; + } + + if (source == OptionSource::ProjectFile) { + if (key == u"include-path") { + parseIncludePathOption(value, HeaderType::Standard); + return true; + } + if (key == u"framework-include-path") { + parseIncludePathOption(value, HeaderType::Framework); + return true; + } + if (key == u"system-include-path") { + parseIncludePathOption(value, HeaderType::System); + return true; + } + } + + return false; +} + +std::shared_ptr<OptionsParser> ApiExtractor::createOptionsParser() +{ + return std::make_shared<ApiExtractorOptionsParser>(d); +} + +struct ApiExtractorPrivate : public ApiExtractorOptions +{ + bool runHelper(ApiExtractorFlags flags); + + static QString getSimplifiedContainerTypeName(const AbstractMetaType &type); + void addInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context, + const AbstractMetaType &type, + const QString &contextName); + void collectInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context, + const AbstractMetaFunctionCPtr &func); + void collectInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context, + const AbstractMetaClassCPtr &metaClass); + void collectInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context); + void collectInstantiatedOpqaqueContainers(InstantiationCollectContext &context); + void collectContainerTypesFromSnippets(InstantiationCollectContext &context); + void collectContainerTypesFromConverterMacros(InstantiationCollectContext &context, + const QString &code, + bool toPythonMacro); + void addInstantiatedSmartPointer(InstantiationCollectContext &context, + const AbstractMetaType &type); + + AbstractMetaBuilder *m_builder = nullptr; +}; + +ApiExtractor::ApiExtractor() : + d(new ApiExtractorPrivate) +{ +} + +ApiExtractor::~ApiExtractor() +{ + delete d->m_builder; + delete d; +} + +HeaderPaths ApiExtractor::includePaths() const +{ + return d->m_includePaths; +} + +void ApiExtractor::setLogDirectory(const QString& logDir) +{ + d->m_logDirectory = logDir; +} + +void ApiExtractor::setCppFileNames(const QFileInfoList &cppFileName) +{ + d->m_cppFileNames = cppFileName; +} + +QFileInfoList ApiExtractor::cppFileNames() const +{ + return d->m_cppFileNames; +} + +void ApiExtractor::setTypeSystem(const QString& typeSystemFileName) +{ + d->m_typeSystemFileName = typeSystemFileName; +} + +QString ApiExtractor::typeSystem() const +{ + return d->m_typeSystemFileName; +} + +const AbstractMetaEnumList &ApiExtractor::globalEnums() const +{ + Q_ASSERT(d->m_builder); + return d->m_builder->globalEnums(); +} + +const AbstractMetaFunctionCList &ApiExtractor::globalFunctions() const +{ + Q_ASSERT(d->m_builder); + return d->m_builder->globalFunctions(); +} + +const AbstractMetaClassList &ApiExtractor::classes() const +{ + Q_ASSERT(d->m_builder); + return d->m_builder->classes(); +} + +const AbstractMetaClassList &ApiExtractor::smartPointers() const +{ + Q_ASSERT(d->m_builder); + return d->m_builder->smartPointers(); +} + +// Add defines required for parsing Qt code headers +static void addPySideExtensions(QByteArrayList *a) +{ + // Make "signals:", "slots:" visible as access specifiers + a->append(QByteArrayLiteral("-DQT_ANNOTATE_ACCESS_SPECIFIER(a)=__attribute__((annotate(#a)))")); + + // Q_PROPERTY is defined as class annotation which does not work since a + // sequence of properties will to expand to a sequence of annotations + // annotating nothing, causing clang to complain. Instead, define it away in a + // static assert with the stringified argument in a ','-operator (cf qdoc). + a->append(QByteArrayLiteral("-DQT_ANNOTATE_CLASS(type,...)=static_assert(sizeof(#__VA_ARGS__),#type);")); + + // With Qt6, qsimd.h became public header and was included in <QtCore>. That + // introduced a conflict with libclang headers on macOS. To be able to include + // <QtCore>, we prevent its inclusion by adding its include guard. + a->append(QByteArrayLiteral("-DQSIMD_H")); +} + +bool ApiExtractorPrivate::runHelper(ApiExtractorFlags flags) +{ + if (m_builder) + return false; + + if (!TypeDatabase::instance()->parseFile(m_typeSystemFileName)) { + std::cerr << "Cannot parse file: " << qPrintable(m_typeSystemFileName); + return false; + } + + const QString pattern = QDir::tempPath() + u'/' + + m_cppFileNames.constFirst().baseName() + "_XXXXXX.hpp"_L1; + QTemporaryFile ppFile(pattern); + bool autoRemove = !qEnvironmentVariableIsSet("KEEP_TEMP_FILES"); + // make sure that a tempfile can be written + if (!ppFile.open()) { + std::cerr << "could not create tempfile " << qPrintable(pattern) + << ": " << qPrintable(ppFile.errorString()) << '\n'; + return false; + } + for (const auto &cppFileName : std::as_const(m_cppFileNames)) { + ppFile.write("#include \""); + ppFile.write(cppFileName.absoluteFilePath().toLocal8Bit()); + ppFile.write("\"\n"); + } + const QString preprocessedCppFileName = ppFile.fileName(); + ppFile.close(); + m_builder = new AbstractMetaBuilder; + m_builder->setLogDirectory(m_logDirectory); + m_builder->setGlobalHeaders(m_cppFileNames); + m_builder->setSkipDeprecated(m_skipDeprecated); + m_builder->setHeaderPaths(m_includePaths); + m_builder->setApiExtractorFlags(flags); + + QByteArrayList arguments; + const auto clangOptionsSize = m_clangOptions.size(); + arguments.reserve(m_includePaths.size() + clangOptionsSize + 1); + + bool addCompilerSupportArguments = true; + if (clangOptionsSize > 0) { + qsizetype i = 0; + if (m_clangOptions.at(i) == u"-") { + ++i; + addCompilerSupportArguments = false; // No built-in options + } + for (; i < clangOptionsSize; ++i) + arguments.append(m_clangOptions.at(i).toUtf8()); + } + + for (const HeaderPath &headerPath : std::as_const(m_includePaths)) + arguments.append(HeaderPath::includeOption(headerPath)); + if (flags.testFlag(ApiExtractorFlag::UsePySideExtensions)) + addPySideExtensions(&arguments); + arguments.append(QFile::encodeName(preprocessedCppFileName)); + + if (ReportHandler::isDebug(ReportHandler::SparseDebug)) { + qCInfo(lcShiboken).noquote().nospace() + << "clang language level: " << int(m_languageLevel) + << "\nclang arguments: " << arguments; + } + + const bool result = m_builder->build(arguments, flags, addCompilerSupportArguments, + m_languageLevel); + if (!result) + autoRemove = false; + if (!autoRemove) { + ppFile.setAutoRemove(false); + std::cerr << "Keeping temporary file: " << qPrintable(QDir::toNativeSeparators(preprocessedCppFileName)) << '\n'; + } + return result; +} + +static inline void classListToCList(const AbstractMetaClassList &list, AbstractMetaClassCList *target) +{ + target->reserve(list.size()); + std::copy(list.cbegin(), list.cend(), std::back_inserter(*target)); +} + +std::optional<ApiExtractorResult> ApiExtractor::run(ApiExtractorFlags flags) +{ + if (!d->runHelper(flags)) + return {}; + InstantiationCollectContext collectContext; + d->collectInstantiatedContainersAndSmartPointers(collectContext); + + ApiExtractorResult result; + classListToCList(d->m_builder->takeClasses(), &result.m_metaClasses); + classListToCList(d->m_builder->takeSmartPointers(), &result.m_smartPointers); + result.m_globalFunctions = d->m_builder->globalFunctions(); + result.m_globalEnums = d->m_builder->globalEnums(); + result.m_enums = d->m_builder->typeEntryToEnumsHash(); + result.m_flags = flags; + result.m_typedefTargetToName = d->m_builder->typedefTargetToName(); + qSwap(result.m_instantiatedContainers, collectContext.instantiatedContainers); + qSwap(result.m_instantiatedSmartPointers, collectContext.instantiatedSmartPointers); + return result; +} + +LanguageLevel ApiExtractor::languageLevel() const +{ + return d->m_languageLevel; +} + +QStringList ApiExtractor::clangOptions() const +{ + return d->m_clangOptions; +} + +AbstractMetaFunctionPtr + ApiExtractor::inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes) +{ + return AbstractMetaBuilder::inheritTemplateFunction(function, templateTypes); +} + +AbstractMetaFunctionPtr + ApiExtractor::inheritTemplateMember(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaClassPtr &subclass) +{ + return AbstractMetaBuilder::inheritTemplateMember(function, templateTypes, + templateClass, subclass); +} + +AbstractMetaClassPtr ApiExtractor::inheritTemplateClass(const ComplexTypeEntryPtr &te, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes) +{ + return AbstractMetaBuilder::inheritTemplateClass(te, templateClass, + templateTypes); +} + +QString ApiExtractorPrivate::getSimplifiedContainerTypeName(const AbstractMetaType &type) +{ + const QString signature = type.cppSignature(); + if (!type.typeEntry()->isContainer() && !type.typeEntry()->isSmartPointer()) + return signature; + QString typeName = signature; + if (type.isConstant()) + typeName.remove(0, sizeof("const ") / sizeof(char) - 1); + switch (type.referenceType()) { + case NoReference: + break; + case LValueReference: + typeName.chop(1); + break; + case RValueReference: + typeName.chop(2); + break; + } + while (typeName.endsWith(u'*') || typeName.endsWith(u' ')) + typeName.chop(1); + return typeName; +} + +// Strip a "const QSharedPtr<const Foo> &" or similar to "QSharedPtr<Foo>" (PYSIDE-1016/454) +AbstractMetaType canonicalSmartPtrInstantiation(const AbstractMetaType &type) +{ + const AbstractMetaTypeList &instantiations = type.instantiations(); + Q_ASSERT(instantiations.size() == 1); + const bool needsFix = type.isConstant() || type.referenceType() != NoReference; + const bool pointeeNeedsFix = instantiations.constFirst().isConstant(); + if (!needsFix && !pointeeNeedsFix) + return type; + auto fixedType = type; + fixedType.setReferenceType(NoReference); + fixedType.setConstant(false); + if (pointeeNeedsFix) { + auto fixedPointeeType = instantiations.constFirst(); + fixedPointeeType.setConstant(false); + fixedType.setInstantiations(AbstractMetaTypeList(1, fixedPointeeType)); + } + return fixedType; +} + +static inline TypeEntryCPtr pointeeTypeEntry(const AbstractMetaType &smartPtrType) +{ + return smartPtrType.instantiations().constFirst().typeEntry(); +} + +static AbstractMetaType simplifiedType(AbstractMetaType type) +{ + type.setIndirections(0); + type.setConstant(false); + type.setReferenceType(NoReference); + type.decideUsagePattern(); + return type; +} + +void +ApiExtractorPrivate::addInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context, + const AbstractMetaType &type, + const QString &contextName) +{ + for (const auto &t : type.instantiations()) + addInstantiatedContainersAndSmartPointers(context, t, contextName); + const auto typeEntry = type.typeEntry(); + const bool isContainer = typeEntry->isContainer(); + if (!isContainer + && !(typeEntry->isSmartPointer() && typeEntry->generateCode())) { + return; + } + if (type.hasTemplateChildren()) { + const auto piece = isContainer ? "container"_L1 : "smart pointer"_L1; + QString warning = + QString::fromLatin1("Skipping instantiation of %1 '%2' because it has template" + " arguments.").arg(piece, type.originalTypeDescription()); + if (!contextName.isEmpty()) + warning.append(" Calling context: "_L1 + contextName); + + qCWarning(lcShiboken).noquote().nospace() << warning; + return; + + } + if (isContainer) { + const QString typeName = getSimplifiedContainerTypeName(type); + if (!context.instantiatedContainersNames.contains(typeName)) { + context.instantiatedContainersNames.append(typeName); + context.instantiatedContainers.append(simplifiedType(type)); + } + return; + } + + // Is smart pointer. Check if the (const?) pointee is already known for the given + // smart pointer type entry. + auto pt = pointeeTypeEntry(type); + const bool present = + std::any_of(context.instantiatedSmartPointers.cbegin(), + context.instantiatedSmartPointers.cend(), + [typeEntry, pt] (const InstantiatedSmartPointer &smp) { + return smp.type.typeEntry() == typeEntry + && pointeeTypeEntry(smp.type) == pt; + }); + if (!present) + addInstantiatedSmartPointer(context, type); +} + +// Create a modification that invalidates the pointee argument of a smart +// pointer constructor or reset(). +static FunctionModification invalidateArgMod(const AbstractMetaFunctionCPtr &f, int index = 1) +{ + ArgumentModification argMod; + argMod.setTargetOwnerShip(TypeSystem::CppOwnership); + argMod.setIndex(index); + FunctionModification funcMod; + funcMod.setSignature(f->minimalSignature()); + funcMod.setArgument_mods({argMod}); + return funcMod; +} + +static void addOwnerModification(const AbstractMetaFunctionCList &functions, + const ComplexTypeEntryPtr &typeEntry) +{ + for (const auto &f : functions) { + if (!f->arguments().isEmpty() + && f->arguments().constFirst().type().indirections() > 0) { + std::const_pointer_cast<AbstractMetaFunction>(f)->clearModificationsCache(); + typeEntry->addFunctionModification(invalidateArgMod(f)); + } + } +} + +void ApiExtractorPrivate::addInstantiatedSmartPointer(InstantiationCollectContext &context, + const AbstractMetaType &type) +{ + InstantiatedSmartPointer smp; + smp.type = canonicalSmartPtrInstantiation(type); + smp.smartPointer = AbstractMetaClass::findClass(m_builder->smartPointers(), + type.typeEntry()); + Q_ASSERT(smp.smartPointer); + + const auto &instantiatedType = type.instantiations().constFirst(); + const auto ste = std::static_pointer_cast<const SmartPointerTypeEntry>(smp.smartPointer->typeEntry()); + QString name = ste->getTargetName(smp.type); + auto parentTypeEntry = ste->parent(); + + // FIXME PYSIDE 7: Make global scope the default behavior? + // Note: Also requires changing SmartPointerTypeEntry::getTargetName() + // to not strip namespaces from unnamed instances. + const bool globalScope = name.startsWith("::"_L1); + if (globalScope) { + name.remove(0, 2); + parentTypeEntry = typeSystemTypeEntry(ste); + } + + auto colonPos = name.lastIndexOf(u"::"); + const bool withinNameSpace = colonPos != -1; + if (withinNameSpace) { // user defined + const QString nameSpace = name.left(colonPos); + name.remove(0, colonPos + 2); + const auto nameSpaces = TypeDatabase::instance()->findNamespaceTypes(nameSpace); + if (nameSpaces.isEmpty()) + throw Exception(msgNamespaceNotFound(name)); + parentTypeEntry = nameSpaces.constFirst(); + } + + TypedefEntryPtr typedefEntry(new TypedefEntry(name, ste->name(), ste->version(), + parentTypeEntry)); + typedefEntry->setTargetLangPackage(ste->targetLangPackage()); + auto instantiationEntry = TypeDatabase::initializeTypeDefEntry(typedefEntry, ste); + instantiationEntry->setParent(parentTypeEntry); + + smp.specialized = ApiExtractor::inheritTemplateClass(instantiationEntry, smp.smartPointer, + {instantiatedType}); + Q_ASSERT(smp.specialized); + if (parentTypeEntry->type() != TypeEntry::TypeSystemType) { // move class to desired namespace + const auto enclClass = AbstractMetaClass::findClass(m_builder->classes(), parentTypeEntry); + Q_ASSERT(enclClass); + auto specialized = std::const_pointer_cast<AbstractMetaClass>(smp.specialized); + specialized->setEnclosingClass(enclClass); + enclClass->addInnerClass(specialized); + } + + if (instantiationEntry->isComplex()) { + addOwnerModification(smp.specialized->queryFunctions(FunctionQueryOption::Constructors), + instantiationEntry); + if (!ste->resetMethod().isEmpty()) { + addOwnerModification(smp.specialized->findFunctions(ste->resetMethod()), + instantiationEntry); + } + } + + context.instantiatedSmartPointers.append(smp); +} + +void +ApiExtractorPrivate::collectInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context, + const AbstractMetaFunctionCPtr &func) +{ + addInstantiatedContainersAndSmartPointers(context, func->type(), func->signature()); + for (const AbstractMetaArgument &arg : func->arguments()) { + const auto argType = arg.type(); + const auto type = argType.viewOn() != nullptr ? *argType.viewOn() : argType; + addInstantiatedContainersAndSmartPointers(context, type, func->signature()); + } +} + +void +ApiExtractorPrivate::collectInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context, + const AbstractMetaClassCPtr &metaClass) +{ + if (!metaClass->typeEntry()->generateCode()) + return; + for (const auto &func : metaClass->functions()) + collectInstantiatedContainersAndSmartPointers(context, func); + for (const auto &func : metaClass->userAddedPythonOverrides()) + collectInstantiatedContainersAndSmartPointers(context, func); + for (const AbstractMetaField &field : metaClass->fields()) + addInstantiatedContainersAndSmartPointers(context, field.type(), field.name()); + + // The list of inner classes might be extended when smart pointer + // instantiations are specified to be in namespaces. + const auto &innerClasses = metaClass->innerClasses(); + for (auto i = innerClasses.size() - 1; i >= 0; --i) { + const auto innerClass = innerClasses.at(i); + if (!innerClass->typeEntry()->isSmartPointer()) + collectInstantiatedContainersAndSmartPointers(context, innerClass); + } +} + +void +ApiExtractorPrivate::collectInstantiatedContainersAndSmartPointers(InstantiationCollectContext &context) +{ + collectInstantiatedOpqaqueContainers(context); + for (const auto &func : m_builder->globalFunctions()) + collectInstantiatedContainersAndSmartPointers(context, func); + for (const auto &metaClass : m_builder->classes()) + collectInstantiatedContainersAndSmartPointers(context, metaClass); + collectContainerTypesFromSnippets(context); +} + +// Whether to generate an opaque container: If the instantiation type is in +// the current package or, for primitive types, if the container is in the +// current package. +static bool generateOpaqueContainer(const AbstractMetaType &type, + const TypeSystemTypeEntryCPtr &moduleEntry) +{ + auto te = type.instantiations().constFirst().typeEntry(); + auto typeModuleEntry = typeSystemTypeEntry(te); + return typeModuleEntry == moduleEntry + || (te->isPrimitive() && typeSystemTypeEntry(type.typeEntry()) == moduleEntry); +} + +void ApiExtractorPrivate::collectInstantiatedOpqaqueContainers(InstantiationCollectContext &context) +{ + // Add all instantiations of opaque containers for types from the current + // module. + auto *td = TypeDatabase::instance(); + const auto moduleEntry = TypeDatabase::instance()->defaultTypeSystemType(); + const auto &containers = td->containerTypes(); + for (const auto &container : containers) { + for (const auto &oc : container->opaqueContainers()) { + QString errorMessage; + const QString typeName = container->qualifiedCppName() + oc.templateParameters(); + auto typeOpt = AbstractMetaType::fromString(typeName, &errorMessage); + if (typeOpt.has_value() + && generateOpaqueContainer(typeOpt.value(), moduleEntry)) { + addInstantiatedContainersAndSmartPointers(context, typeOpt.value(), + u"opaque containers"_s); + } + } + } +} + +static void getCode(QStringList &code, const CodeSnipList &codeSnips) +{ + for (const CodeSnip &snip : std::as_const(codeSnips)) + code.append(snip.code()); +} + +static void getCode(QStringList &code, const TypeEntryCPtr &type) +{ + if (type->isComplex()) + getCode(code, std::static_pointer_cast<const ComplexTypeEntry>(type)->codeSnips()); + else if (type->isTypeSystem()) + getCode(code, std::static_pointer_cast<const TypeSystemTypeEntry>(type)->codeSnips()); + + auto customConversion = CustomConversion::getCustomConversion(type); + if (!customConversion) + return; + + if (!customConversion->nativeToTargetConversion().isEmpty()) + code.append(customConversion->nativeToTargetConversion()); + + const auto &toCppConversions = customConversion->targetToNativeConversions(); + if (toCppConversions.isEmpty()) + return; + + for (const auto &toNative : std::as_const(toCppConversions)) + code.append(toNative.conversion()); +} + +void ApiExtractorPrivate::collectContainerTypesFromSnippets(InstantiationCollectContext &context) +{ + QStringList snips; + auto *td = TypeDatabase::instance(); + const PrimitiveTypeEntryCList &primitiveTypeList = td->primitiveTypes(); + for (const auto &type : primitiveTypeList) + getCode(snips, type); + const ContainerTypeEntryCList &containerTypeList = td->containerTypes(); + for (const auto &type : containerTypeList) + getCode(snips, type); + for (const auto &metaClass : m_builder->classes()) + getCode(snips, metaClass->typeEntry()); + + const auto moduleEntry = td->defaultTypeSystemType(); + Q_ASSERT(moduleEntry); + getCode(snips, moduleEntry); + + for (const auto &func : m_builder->globalFunctions()) + getCode(snips, func->injectedCodeSnips()); + + for (const QString &code : std::as_const(snips)) { + collectContainerTypesFromConverterMacros(context, code, true); + collectContainerTypesFromConverterMacros(context, code, false); + } +} + +void +ApiExtractorPrivate::collectContainerTypesFromConverterMacros(InstantiationCollectContext &context, + const QString &code, + bool toPythonMacro) +{ + QString convMacro = toPythonMacro ? u"%CONVERTTOPYTHON["_s : u"%CONVERTTOCPP["_s; + const qsizetype offset = toPythonMacro ? sizeof("%CONVERTTOPYTHON") : sizeof("%CONVERTTOCPP"); + qsizetype start = 0; + QString errorMessage; + while ((start = code.indexOf(convMacro, start)) != -1) { + int end = code.indexOf(u']', start); + start += offset; + if (code.at(start) != u'%') { + QString typeString = code.mid(start, end - start); + auto type = AbstractMetaType::fromString(typeString, &errorMessage); + if (type.has_value()) { + const QString &d = type->originalTypeDescription(); + addInstantiatedContainersAndSmartPointers(context, type.value(), d); + } else { + QString m; + QTextStream(&m) << __FUNCTION__ << ": Cannot translate type \"" + << typeString << "\": " << errorMessage; + throw Exception(m); + } + } + start = end; + } +} + +#ifndef QT_NO_DEBUG_STREAM +template <class Container> +static void debugFormatSequence(QDebug &d, const char *key, const Container& c) +{ + if (c.isEmpty()) + return; + const auto begin = c.begin(); + d << "\n " << key << '[' << c.size() << "]=("; + for (auto it = begin, end = c.end(); it != end; ++it) { + if (it != begin) + d << ", "; + d << *it; + } + d << ')'; +} + +QDebug operator<<(QDebug d, const ApiExtractor &ae) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + if (ReportHandler::debugLevel() >= ReportHandler::FullDebug) + d.setVerbosity(3); // Trigger verbose output of AbstractMetaClass + d << "ApiExtractor(typeSystem=\"" << ae.typeSystem() << "\", cppFileNames=\"" + << ae.cppFileNames() << ", "; + ae.d->m_builder->formatDebug(d); + d << ')'; + return d; +} +#endif // QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/apiextractor.h b/sources/shiboken6/ApiExtractor/apiextractor.h new file mode 100644 index 000000000..7bff5c252 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/apiextractor.h @@ -0,0 +1,87 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef APIEXTRACTOR_H +#define APIEXTRACTOR_H + +#include "abstractmetalang_typedefs.h" +#include "apiextractorflags.h" +#include "header_paths.h" +#include "clangparser/compilersupport.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QFileInfoList> +#include <QtCore/QStringList> + +#include <optional> + +class ApiExtractorResult; +class AbstractMetaClass; +class AbstractMetaEnum; +class AbstractMetaFunction; +class ComplexTypeEntry; +struct OptionDescription; +class OptionsParser; + +QT_BEGIN_NAMESPACE +class QDebug; +class QIODevice; +QT_END_NAMESPACE + +struct ApiExtractorPrivate; + +class ApiExtractor +{ +public: + Q_DISABLE_COPY_MOVE(ApiExtractor) + + ApiExtractor(); + ~ApiExtractor(); + + static QList<OptionDescription> options(); + std::shared_ptr<OptionsParser> createOptionsParser(); + + void setTypeSystem(const QString& typeSystemFileName); + QString typeSystem() const; + void setCppFileNames(const QFileInfoList &cppFileNames); + QFileInfoList cppFileNames() const; + HeaderPaths includePaths() const; + void setLogDirectory(const QString& logDir); + LanguageLevel languageLevel() const; + QStringList clangOptions() const; + + const AbstractMetaEnumList &globalEnums() const; + const AbstractMetaFunctionCList &globalFunctions() const; + const AbstractMetaClassList &classes() const; + const AbstractMetaClassList &smartPointers() const; + + std::optional<ApiExtractorResult> run(ApiExtractorFlags flags); + + /// Forwards to AbstractMetaBuilder::inheritTemplateFunction() + static AbstractMetaFunctionPtr + inheritTemplateFunction(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes); + + /// Forwards to AbstractMetaBuilder::inheritTemplateMember() + static AbstractMetaFunctionPtr + inheritTemplateMember(const AbstractMetaFunctionCPtr &function, + const AbstractMetaTypeList &templateTypes, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaClassPtr &subclass); + + /// Forwards to AbstractMetaBuilder::inheritTemplateClass() + static AbstractMetaClassPtr + inheritTemplateClass(const ComplexTypeEntryPtr &te, + const AbstractMetaClassCPtr &templateClass, + const AbstractMetaTypeList &templateTypes); + +private: + ApiExtractorPrivate *d; + +#ifndef QT_NO_DEBUG_STREAM + friend QDebug operator<<(QDebug d, const ApiExtractor &ae); +#endif +}; + +#endif // APIEXTRACTOR_H + diff --git a/sources/shiboken6/ApiExtractor/apiextractorflags.h b/sources/shiboken6/ApiExtractor/apiextractorflags.h new file mode 100644 index 000000000..6f69b8b77 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/apiextractorflags.h @@ -0,0 +1,18 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef APIEXTRACTORFLAGS_H +#define APIEXTRACTORFLAGS_H + +#include <QtCore/QFlags> + +enum class ApiExtractorFlag +{ + UsePySideExtensions = 0x1, + AvoidProtectedHack = 0x2 +}; + +Q_DECLARE_FLAGS(ApiExtractorFlags, ApiExtractorFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(ApiExtractorFlags) + +#endif // APIEXTRACTORFLAGS_H diff --git a/sources/shiboken6/ApiExtractor/apiextractorresult.cpp b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp new file mode 100644 index 000000000..b53ffa9b6 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "apiextractorresult.h" +#include "abstractmetalang.h" +#include "abstractmetaenum.h" + +#include "enumtypeentry.h" +#include "flagstypeentry.h" + +ApiExtractorResult::ApiExtractorResult() = default; + +ApiExtractorResult::ApiExtractorResult(const ApiExtractorResult &) = default; + +ApiExtractorResult &ApiExtractorResult::operator=(const ApiExtractorResult &) = default; + +ApiExtractorResult::ApiExtractorResult(ApiExtractorResult &&) noexcept = default; + +ApiExtractorResult &ApiExtractorResult::operator=(ApiExtractorResult &&) noexcept = default; + +ApiExtractorResult::~ApiExtractorResult() = default; + +const AbstractMetaEnumList &ApiExtractorResult::globalEnums() const +{ + return m_globalEnums; +} + +const AbstractMetaFunctionCList &ApiExtractorResult::globalFunctions() const +{ + return m_globalFunctions; +} + +const AbstractMetaClassCList &ApiExtractorResult::classes() const +{ + return m_metaClasses; +} + +const AbstractMetaClassCList &ApiExtractorResult::smartPointers() const +{ + return m_smartPointers; +} + +const AbstractMetaTypeList &ApiExtractorResult::instantiatedContainers() const +{ + return m_instantiatedContainers; +} + +const InstantiatedSmartPointers &ApiExtractorResult::instantiatedSmartPointers() const +{ + return m_instantiatedSmartPointers; +} + +std::optional<InstantiatedSmartPointer> + ApiExtractorResult::findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const +{ + for (const auto &smp : m_instantiatedSmartPointers) { + const auto &i = smp.type; + if (i.typeEntry() == pointer && i.instantiations().at(0).typeEntry() == pointee) + return smp; + } + return std::nullopt; +} + +const QMultiHash<QString, QString> &ApiExtractorResult::typedefTargetToName() const +{ + return m_typedefTargetToName; +} + +ApiExtractorFlags ApiExtractorResult::flags() const +{ + return m_flags; +} + +void ApiExtractorResult::setFlags(ApiExtractorFlags f) +{ + m_flags = f; +} + +std::optional<AbstractMetaEnum> + ApiExtractorResult::findAbstractMetaEnum(TypeEntryCPtr typeEntry) const +{ + if (typeEntry && typeEntry->isFlags()) + typeEntry = std::static_pointer_cast<const FlagsTypeEntry>(typeEntry)->originator(); + const auto it = m_enums.constFind(typeEntry); + if (it == m_enums.constEnd()) + return {}; + return it.value(); +} + +AbstractMetaFunctionCList ApiExtractorResult::implicitConversions(const TypeEntryCPtr &type) const +{ + if (type->isValue()) { + if (auto metaClass = AbstractMetaClass::findClass(m_metaClasses, type)) + return metaClass->implicitConversions(); + } + return {}; +} + +AbstractMetaFunctionCList ApiExtractorResult::implicitConversions(const AbstractMetaType &metaType) const +{ + return implicitConversions(metaType.typeEntry()); +} diff --git a/sources/shiboken6/ApiExtractor/apiextractorresult.h b/sources/shiboken6/ApiExtractor/apiextractorresult.h new file mode 100644 index 000000000..b2aae88ed --- /dev/null +++ b/sources/shiboken6/ApiExtractor/apiextractorresult.h @@ -0,0 +1,81 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef APIEXTRACTORRESULT_H +#define APIEXTRACTORRESULT_H + +#include "apiextractorflags.h" +#include "abstractmetatype.h" +#include "abstractmetalang_typedefs.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QHash> +#include <QtCore/QMultiHash> + +#include <optional> + +class ApiExtractorResultData; + +struct InstantiatedSmartPointer +{ + AbstractMetaClassCPtr smartPointer; // Template class + AbstractMetaClassCPtr specialized; // Specialized for type + AbstractMetaType type; +}; + +using InstantiatedSmartPointers = QList<InstantiatedSmartPointer>; + +/// Result of an ApiExtractor run. +class ApiExtractorResult +{ +public: + ApiExtractorResult(); + ApiExtractorResult(const ApiExtractorResult &); + ApiExtractorResult &operator=(const ApiExtractorResult &); + ApiExtractorResult(ApiExtractorResult &&) noexcept; + ApiExtractorResult &operator=(ApiExtractorResult &&) noexcept; + ~ApiExtractorResult(); + + const AbstractMetaEnumList &globalEnums() const; + const AbstractMetaFunctionCList &globalFunctions() const; + const AbstractMetaClassCList &classes() const; + const AbstractMetaClassCList &smartPointers() const; + + const AbstractMetaTypeList &instantiatedContainers() const; + const InstantiatedSmartPointers &instantiatedSmartPointers() const; + std::optional<InstantiatedSmartPointer> + findSmartPointerInstantiation(const SmartPointerTypeEntryCPtr &pointer, + const TypeEntryCPtr &pointee) const; + + const QMultiHash<QString, QString> &typedefTargetToName() const; + + // Query functions for the generators + std::optional<AbstractMetaEnum> + findAbstractMetaEnum(TypeEntryCPtr typeEntry) const; + + /// Retrieves a list of constructors used in implicit conversions + /// available on the given type. The TypeEntry must be a value-type + /// or else it will return an empty list. + /// \param type a TypeEntry that is expected to be a value-type + /// \return a list of constructors that could be used as implicit converters + AbstractMetaFunctionCList implicitConversions(const TypeEntryCPtr &type) const; + AbstractMetaFunctionCList implicitConversions(const AbstractMetaType &metaType) const; + + ApiExtractorFlags flags() const; + void setFlags(ApiExtractorFlags f); + +private: + AbstractMetaClassCList m_metaClasses; + AbstractMetaClassCList m_smartPointers; + AbstractMetaFunctionCList m_globalFunctions; + AbstractMetaEnumList m_globalEnums; + AbstractMetaTypeList m_instantiatedContainers; + InstantiatedSmartPointers m_instantiatedSmartPointers; + QHash<TypeEntryCPtr, AbstractMetaEnum> m_enums; + QMultiHash<QString, QString> m_typedefTargetToName; + ApiExtractorFlags m_flags; + + friend class ApiExtractor; +}; + +#endif // APIEXTRACTORRESULT_H diff --git a/sources/shiboken6/ApiExtractor/arraytypeentry.h b/sources/shiboken6/ApiExtractor/arraytypeentry.h new file mode 100644 index 000000000..5b9bb191e --- /dev/null +++ b/sources/shiboken6/ApiExtractor/arraytypeentry.h @@ -0,0 +1,28 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ARRAYTYPEENTRY_H +#define ARRAYTYPEENTRY_H + +#include "typesystem.h" + +class ArrayTypeEntryPrivate; + +class ArrayTypeEntry : public TypeEntry +{ +public: + explicit ArrayTypeEntry(const TypeEntryCPtr &nested_type, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + void setNestedTypeEntry(const TypeEntryPtr &nested); + TypeEntryCPtr nestedTypeEntry() const; + + TypeEntry *clone() const override; + +protected: + explicit ArrayTypeEntry(ArrayTypeEntryPrivate *d); + + QString buildTargetLangName() const override; +}; + +#endif // ARRAYTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp new file mode 100644 index 000000000..31e7efb05 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp @@ -0,0 +1,1296 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clangbuilder.h" +#include "compilersupport.h" +#include "clangutils.h" +#include "clangdebugutils.h" + +#include <codemodel.h> +#include <reporthandler.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QHash> +#include <QtCore/QMap> +#include <QtCore/QString> +#include <QtCore/QStack> +#include <QtCore/QList> + +#include <cstring> +#include <ctype.h> + +using namespace Qt::StringLiterals; + +namespace clang { + +static inline bool isClassCursor(const CXCursor &c) +{ + return c.kind == CXCursor_ClassDecl || c.kind == CXCursor_StructDecl + || c.kind == CXCursor_ClassTemplate + || c.kind == CXCursor_ClassTemplatePartialSpecialization; +} + +static inline bool isClassOrNamespaceCursor(const CXCursor &c) +{ + return c.kind == CXCursor_Namespace || isClassCursor(c); +} + +static inline bool withinClassDeclaration(const CXCursor &cursor) +{ + return isClassCursor(clang_getCursorLexicalParent(cursor)); +} + +static QString fixTypeName(QString t) +{ + // Fix "Foo &" -> "Foo&", similarly "Bar **" -> "Bar**" + auto pos = t.size() - 1; + for (; pos >= 0 && (t.at(pos) == u'&' || t.at(pos) == u'*'); --pos) {} + if (pos > 0 && t.at(pos) == u' ') + t.remove(pos, 1); + return t; +} + +// Insert template parameter to class name: "Foo<>" -> "Foo<T1>" -> "Foo<T1,T2>" +// This needs to be done immediately when template parameters are encountered since +// the class name "Foo<T1,T2>" is the scope for nested items. +static bool insertTemplateParameterIntoClassName(const QString &parmName, QString *name) +{ + if (Q_UNLIKELY(!name->endsWith(u'>'))) + return false; + const bool needsComma = name->at(name->size() - 2) != u'<'; + const auto insertionPos = name->size() - 1; + name->insert(insertionPos, parmName); + if (needsComma) + name->insert(insertionPos, u','); + return true; +} + +static inline bool insertTemplateParameterIntoClassName(const QString &parmName, + const ClassModelItem &item) +{ + QString name = item->name(); + const bool result = insertTemplateParameterIntoClassName(parmName, &name); + item->setName(name); + return result; +} + +static inline Access accessPolicy(CX_CXXAccessSpecifier access) +{ + Access result = Access::Public; + switch (access) { + case CX_CXXProtected: + result = Access::Protected; + break; + case CX_CXXPrivate: + result = Access::Private; + break; + default: + break; + } + return result; +} + +static bool isSigned(CXTypeKind kind) +{ + switch (kind) { + case CXType_UChar: + case CXType_Char16: + case CXType_Char32: + case CXType_UShort: + case CXType_UInt: + case CXType_ULong: + case CXType_ULongLong: + case CXType_UInt128: + return false; + default: + break; + } + return true; +} + +class BuilderPrivate { +public: + Q_DISABLE_COPY_MOVE(BuilderPrivate) + + enum class SpecialSystemHeader { + None, + Types, + OpenGL, + WhiteListed, + WhiteListedPath + }; + + using CursorClassHash = QHash<CXCursor, ClassModelItem>; + using TypeInfoHash = QHash<CXType, TypeInfo>; + + explicit BuilderPrivate(BaseVisitor *bv) : m_baseVisitor(bv), m_model(new CodeModel) + { + m_scopeStack.push(NamespaceModelItem(new _FileModelItem(m_model))); + } + ~BuilderPrivate() + { + delete m_model; + } + + // Determine scope from top item. Note that the scope list does not necessarily + // match the scope stack in case of forward-declared inner classes whose definition + // appears in the translation unit while the scope is the outer class. + void updateScope() + { + if (m_scopeStack.size() <= 1) + m_scope.clear(); + else + m_scope = m_scopeStack.back()->scope() << m_scopeStack.back()->name(); + } + + void pushScope(const ScopeModelItem &i) + { + m_scopeStack.push(i); + updateScope(); + } + + void popScope() + { + m_scopeStack.back()->purgeClassDeclarations(); + m_scopeStack.pop(); + updateScope(); + } + + bool addClass(const CXCursor &cursor, CodeModel::ClassType t); + FunctionModelItem createFunction(const CXCursor &cursor, + CodeModel::FunctionType t = CodeModel::Normal, + bool isTemplateCode = false); + FunctionModelItem createMemberFunction(const CXCursor &cursor, + bool isTemplateCode = false); + void qualifyConstructor(const CXCursor &cursor); + TypeInfo createTypeInfoUncached(const CXType &type, + bool *cacheable = nullptr) const; + TypeInfo createTypeInfo(const CXType &type) const; + TypeInfo createTypeInfo(const CXCursor &cursor) const + { return createTypeInfo(clang_getCursorType(cursor)); } + void addTemplateInstantiations(const CXType &type, + QString *typeName, + TypeInfo *t) const; + bool addTemplateInstantiationsRecursion(const CXType &type, TypeInfo *t) const; + + void addTypeDef(const CXCursor &cursor, const CXType &cxType); + ClassModelItem currentTemplateClass() const; + void startTemplateTypeAlias(const CXCursor &cursor); + void endTemplateTypeAlias(const CXCursor &typeAliasCursor); + + TemplateParameterModelItem createTemplateParameter(const CXCursor &cursor) const; + TemplateParameterModelItem createNonTypeTemplateParameter(const CXCursor &cursor) const; + void addField(const CXCursor &cursor); + + static QString cursorValueExpression(BaseVisitor *bv, const CXCursor &cursor); + std::pair<QString, ClassModelItem> getBaseClass(CXType type) const; + void addBaseClass(const CXCursor &cursor); + + SpecialSystemHeader specialSystemHeader(const QString &fileName) const; + bool visitHeader(const QString &fileName) const; + static const char *specialSystemHeaderReason(SpecialSystemHeader sh); + + void setFileName(const CXCursor &cursor, _CodeModelItem *item); + + BaseVisitor *m_baseVisitor; + CodeModel *m_model; + + QStack<ScopeModelItem> m_scopeStack; + QStringList m_scope; + // Store all classes by cursor so that base classes can be found and inner + // classes can be correctly parented in case of forward-declared inner classes + // (QMetaObject::Connection) + CursorClassHash m_cursorClassHash; + + mutable TypeInfoHash m_typeInfoHash; // Cache type information + mutable QHash<QString, TemplateTypeAliasModelItem> m_templateTypeAliases; + + ClassModelItem m_currentClass; + EnumModelItem m_currentEnum; + FunctionModelItem m_currentFunction; + ArgumentModelItem m_currentArgument; + VariableModelItem m_currentField; + TemplateTypeAliasModelItem m_currentTemplateTypeAlias; + QStringList m_forceProcessSystemIncludes; // files, like "memory" + QStringList m_forceProcessSystemIncludePaths; // paths, like "/usr/include/Qt/" + QString m_usingTypeRef; // Base classes in "using Base::member;" + bool m_withinUsingDeclaration = false; + + int m_anonymousEnumCount = 0; + CodeModel::FunctionType m_currentFunctionType = CodeModel::Normal; + bool m_withinFriendDecl = false; + mutable QHash<QString, SpecialSystemHeader> m_systemHeaders; +}; + +bool BuilderPrivate::addClass(const CXCursor &cursor, CodeModel::ClassType t) +{ + QString className = getCursorSpelling(cursor); + m_currentClass.reset(new _ClassModelItem(m_model, className)); + setFileName(cursor, m_currentClass.get()); + m_currentClass->setClassType(t); + // Some inner class? Note that it does not need to be (lexically) contained in a + // class since it is possible to forward declare an inner class: + // class QMetaObject { class Connection; } + // class QMetaObject::Connection {} + const CXCursor semPar = clang_getCursorSemanticParent(cursor); + if (isClassCursor(semPar)) { + const CursorClassHash::const_iterator it = m_cursorClassHash.constFind(semPar); + if (it == m_cursorClassHash.constEnd()) { + QString message; + QTextStream(&message) << "Unable to find containing class \"" + << getCursorSpelling(semPar) << "\" of inner class \"" + << className << "\"."; + // PYSIDE-1501: Has been observed to fail for inner class of + // template with separated implementation where a forward + // declaration of the outer template is reported (Boost). + const auto severity = semPar.kind == CXCursor_ClassTemplate + ? CXDiagnostic_Warning : CXDiagnostic_Error; + const Diagnostic d(message, cursor, severity); + qWarning() << d; + m_baseVisitor->appendDiagnostic(d); + return false; + } + const ClassModelItem &containingClass = it.value(); + containingClass->addClass(m_currentClass); + m_currentClass->setScope(containingClass->scope() << containingClass->name()); + } else { + m_currentClass->setScope(m_scope); + m_scopeStack.back()->addClass(m_currentClass); + } + pushScope(m_currentClass); + m_cursorClassHash.insert(cursor, m_currentClass); + return true; +} + +static QString msgCannotDetermineException(const std::string_view &snippetV) +{ + const auto newLine = snippetV.find('\n'); // Multiline noexcept specifications have been found in Qt + const bool truncate = newLine != std::string::npos; + const qsizetype length = qsizetype(truncate ? newLine : snippetV.size()); + QString snippet = QString::fromUtf8(snippetV.data(), length); + if (truncate) + snippet += "..."_L1; + + return u"Cannot determine exception specification: \""_s + snippet + u'"'; +} + +// Return whether noexcept(<value>) throws. noexcept() takes a constexpr value. +// Try to determine the simple cases (true|false) via code snippet. +static ExceptionSpecification computedExceptionSpecificationFromClang(BaseVisitor *bv, + const CXCursor &cursor, + bool isTemplateCode) +{ + const std::string_view snippet = bv->getCodeSnippet(cursor); + if (snippet.empty()) + return ExceptionSpecification::Unknown; // Macro expansion, cannot tell + if (snippet.find("noexcept(false)") != std::string::npos) + return ExceptionSpecification::Throws; + if (snippet.find("noexcept(true)") != std::string::npos) + return ExceptionSpecification::NoExcept; + // Warn about it unless it is some form of template code where it is common + // to have complicated code, which is of no concern to shiboken, like: + // "QList::emplace(T) noexcept(is_pod<T>)". + if (!isTemplateCode && ReportHandler::isDebug(ReportHandler::FullDebug)) { + const Diagnostic d(msgCannotDetermineException(snippet), cursor, CXDiagnostic_Warning); + qWarning() << d; + bv->appendDiagnostic(d); + } + return ExceptionSpecification::Unknown; +} + +static ExceptionSpecification exceptionSpecificationFromClang(BaseVisitor *bv, + const CXCursor &cursor, + bool isTemplateCode) +{ + const auto ce = clang_getCursorExceptionSpecificationType(cursor); + switch (ce) { + case CXCursor_ExceptionSpecificationKind_ComputedNoexcept: + return computedExceptionSpecificationFromClang(bv, cursor, isTemplateCode); + case CXCursor_ExceptionSpecificationKind_BasicNoexcept: + case CXCursor_ExceptionSpecificationKind_DynamicNone: // throw() + case CXCursor_ExceptionSpecificationKind_NoThrow: + return ExceptionSpecification::NoExcept; + case CXCursor_ExceptionSpecificationKind_Dynamic: // throw(t1..) + case CXCursor_ExceptionSpecificationKind_MSAny: // throw(...) + return ExceptionSpecification::Throws; + default: + // CXCursor_ExceptionSpecificationKind_None, + // CXCursor_ExceptionSpecificationKind_Unevaluated, + // CXCursor_ExceptionSpecificationKind_Uninstantiated + break; + } + return ExceptionSpecification::Unknown; +} + +FunctionModelItem BuilderPrivate::createFunction(const CXCursor &cursor, + CodeModel::FunctionType t, + bool isTemplateCode) +{ + QString name = getCursorSpelling(cursor); + // Apply type fixes to "operator X &" -> "operator X&" + if (name.startsWith(u"operator ")) + name = fixTypeName(name); + auto result = std::make_shared<_FunctionModelItem>(m_model, name); + setFileName(cursor, result.get()); + const auto type = clang_getCursorResultType(cursor); + result->setType(createTypeInfo(type)); + result->setScopeResolution(hasScopeResolution(type)); + result->setFunctionType(t); + result->setScope(m_scope); + result->setStatic(clang_Cursor_getStorageClass(cursor) == CX_SC_Static); + result->setExceptionSpecification(exceptionSpecificationFromClang(m_baseVisitor, cursor, isTemplateCode)); + switch (clang_getCursorAvailability(cursor)) { + case CXAvailability_Available: + break; + case CXAvailability_Deprecated: + result->setAttribute(FunctionAttribute::Deprecated); + break; + case CXAvailability_NotAvailable: // "Foo(const Foo&) = delete;" + result->setDeleted(true); + break; + case CXAvailability_NotAccessible: + break; + } + return result; +} + +static inline CodeModel::FunctionType functionTypeFromCursor(const CXCursor &cursor) +{ + CodeModel::FunctionType result = CodeModel::Normal; + switch (cursor.kind) { + case CXCursor_Constructor: + if (clang_CXXConstructor_isCopyConstructor(cursor) != 0) + result = CodeModel::CopyConstructor; + else if (clang_CXXConstructor_isMoveConstructor(cursor) != 0) + result = CodeModel::MoveConstructor; + else + result = CodeModel::Constructor; + break; + case CXCursor_Destructor: + result = CodeModel::Destructor; + break; + default: + break; + } + return result; +} + +FunctionModelItem BuilderPrivate::createMemberFunction(const CXCursor &cursor, + bool isTemplateCode) +{ + const CodeModel::FunctionType functionType = + m_currentFunctionType == CodeModel::Signal || m_currentFunctionType == CodeModel::Slot + ? m_currentFunctionType // by annotation + : functionTypeFromCursor(cursor); + isTemplateCode |= m_currentClass->name().endsWith(u'>'); + auto result = createFunction(cursor, functionType, isTemplateCode); + result->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); + result->setConstant(clang_CXXMethod_isConst(cursor) != 0); + result->setAttribute(FunctionAttribute::Static, clang_CXXMethod_isStatic(cursor) != 0); + result->setAttribute(FunctionAttribute::Virtual, clang_CXXMethod_isVirtual(cursor) != 0); + result->setAttribute(FunctionAttribute::Abstract, clang_CXXMethod_isPureVirtual(cursor) != 0); + return result; +} + +// For CXCursor_Constructor, on endToken(). +void BuilderPrivate::qualifyConstructor(const CXCursor &cursor) +{ + // Clang does not tell us whether a constructor is explicit, preventing it + // from being used for implicit conversions. Try to guess whether a + // constructor is explicit in the C++99 sense (1 parameter) by checking for + // isConvertingConstructor() == 0. Fixme: The notion of "isConvertingConstructor" + // should be used in the code model instead of "explicit" + if (clang_CXXConstructor_isDefaultConstructor(cursor) == 0 + && m_currentFunction->arguments().size() == 1 + && clang_CXXConstructor_isCopyConstructor(cursor) == 0 + && clang_CXXConstructor_isMoveConstructor(cursor) == 0) { + m_currentFunction->setAttribute(FunctionAttribute::Explicit, + clang_CXXConstructor_isConvertingConstructor(cursor) == 0); + } +} + +TemplateParameterModelItem BuilderPrivate::createTemplateParameter(const CXCursor &cursor) const +{ + return std::make_shared<_TemplateParameterModelItem>(m_model, getCursorSpelling(cursor)); +} + +TemplateParameterModelItem BuilderPrivate::createNonTypeTemplateParameter(const CXCursor &cursor) const +{ + TemplateParameterModelItem result = createTemplateParameter(cursor); + result->setType(createTypeInfo(clang_getCursorType(cursor))); + return result; +} + +// CXCursor_VarDecl, CXCursor_FieldDecl cursors +void BuilderPrivate::addField(const CXCursor &cursor) +{ + auto field = std::make_shared<_VariableModelItem>(m_model, getCursorSpelling(cursor)); + field->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); + field->setScope(m_scope); + field->setType(createTypeInfo(cursor)); + field->setMutable(clang_CXXField_isMutable(cursor) != 0); + setFileName(cursor, field.get()); + m_currentField = field; + m_scopeStack.back()->addVariable(field); +} + +// Create qualified name "std::list<std::string>" -> ("std", "list<std::string>") +static QStringList qualifiedName(const QString &t) +{ + QStringList result; + int end = t.indexOf(u'<'); + if (end == -1) + end = t.indexOf(u'('); + if (end == -1) + end = t.size(); + int lastPos = 0; + while (true) { + const int nextPos = t.indexOf(u"::"_s, lastPos); + if (nextPos < 0 || nextPos >= end) + break; + result.append(t.mid(lastPos, nextPos - lastPos)); + lastPos = nextPos + 2; + } + result.append(t.right(t.size() - lastPos)); + return result; +} + +static bool isArrayType(CXTypeKind k) +{ + return k == CXType_ConstantArray || k == CXType_IncompleteArray + || k == CXType_VariableArray || k == CXType_DependentSizedArray; +} + +static bool isPointerType(CXTypeKind k) +{ + return k == CXType_Pointer || k == CXType_LValueReference || k == CXType_RValueReference; +} + +bool BuilderPrivate::addTemplateInstantiationsRecursion(const CXType &type, TypeInfo *t) const +{ + // Template arguments + switch (type.kind) { + case CXType_Elaborated: + case CXType_Record: + case CXType_Unexposed: + if (const int numTemplateArguments = qMax(0, clang_Type_getNumTemplateArguments(type))) { + for (unsigned tpl = 0; tpl < unsigned(numTemplateArguments); ++tpl) { + const CXType argType = clang_Type_getTemplateArgumentAsType(type, tpl); + // CXType_Invalid is returned when hitting on a specialization + // of a non-type template (template <int v>). + if (argType.kind == CXType_Invalid) + return false; + t->addInstantiation(createTypeInfoUncached(argType)); + } + } + break; + default: + break; + } + return true; +} + +static void dummyTemplateArgumentHandler(int, QStringView) {} + +void BuilderPrivate::addTemplateInstantiations(const CXType &type, + QString *typeName, + TypeInfo *t) const +{ + // In most cases, for templates like "Vector<A>", Clang will give us the + // arguments by recursing down the type. However this will fail for example + // within template classes (for functions like the copy constructor): + // template <class T> + // class Vector { + // Vector(const Vector&); + // }; + // In that case, have TypeInfo parse the list from the spelling. + // Finally, remove the list "<>" from the type name. + const bool parsed = addTemplateInstantiationsRecursion(type, t) + && !t->instantiations().isEmpty(); + if (!parsed) + t->setInstantiations({}); + const auto pos = parsed + ? parseTemplateArgumentList(*typeName, dummyTemplateArgumentHandler) + : t->parseTemplateArgumentList(*typeName); + if (pos.first != -1 && pos.second != -1 && pos.second > pos.first) + typeName->remove(pos.first, pos.second - pos.first); +} + +TypeInfo BuilderPrivate::createTypeInfoUncached(const CXType &type, + bool *cacheable) const +{ + if (type.kind == CXType_Pointer) { // Check for function pointers, first. + const CXType pointeeType = clang_getPointeeType(type); + const int argCount = clang_getNumArgTypes(pointeeType); + if (argCount >= 0) { + TypeInfo result = createTypeInfoUncached(clang_getResultType(pointeeType), + cacheable); + result.setFunctionPointer(true); + for (int a = 0; a < argCount; ++a) + result.addArgument(createTypeInfoUncached(clang_getArgType(pointeeType, unsigned(a)), + cacheable)); + return result; + } + } + + TypeInfo typeInfo; + + CXType nestedType = type; + for (; isArrayType(nestedType.kind); nestedType = clang_getArrayElementType(nestedType)) { + const long long size = clang_getArraySize(nestedType); + typeInfo.addArrayElement(size >= 0 ? QString::number(size) : QString()); + } + + TypeInfo::Indirections indirections; + for (; isPointerType(nestedType.kind); nestedType = clang_getPointeeType(nestedType)) { + switch (nestedType.kind) { + case CXType_Pointer: + indirections.prepend(clang_isConstQualifiedType(nestedType) != 0 + ? Indirection::ConstPointer : Indirection::Pointer); + break; + case CXType_LValueReference: + typeInfo.setReferenceType(LValueReference); + break; + case CXType_RValueReference: + typeInfo.setReferenceType(RValueReference); + break; + default: + break; + } + } + typeInfo.setIndirectionsV(indirections); + + typeInfo.setConstant(clang_isConstQualifiedType(nestedType) != 0); + typeInfo.setVolatile(clang_isVolatileQualifiedType(nestedType) != 0); + + QString typeName = getResolvedTypeName(nestedType); + while (TypeInfo::stripLeadingConst(&typeName) + || TypeInfo::stripLeadingVolatile(&typeName)) { + } + + // For typedefs within templates or nested classes within templates (iterators): + // "template <class T> class QList { using Value=T; .." + // the typedef source is named "type-parameter-0-0". Convert it back to the + // template parameter name. The CXTypes are the same for all templates and + // must not be cached. + if (m_currentClass && typeName.startsWith(u"type-parameter-0-")) { + if (cacheable != nullptr) + *cacheable = false; + bool ok; + const int n = QStringView{typeName}.mid(17).toInt(&ok); + if (ok) { + auto currentTemplate = currentTemplateClass(); + if (currentTemplate && n < currentTemplate->templateParameters().size()) + typeName = currentTemplate->templateParameters().at(n)->name(); + } + } + + // Obtain template instantiations if the name has '<' (thus excluding + // typedefs like "std::string". + if (typeName.contains(u'<')) + addTemplateInstantiations(nestedType, &typeName, &typeInfo); + + typeInfo.setQualifiedName(qualifiedName(typeName)); + // 3320:CINDEX_LINKAGE int clang_getNumArgTypes(CXType T); function ptr types? + typeInfo.simplifyStdType(); + return typeInfo; +} + +TypeInfo BuilderPrivate::createTypeInfo(const CXType &type) const +{ + const auto it = m_typeInfoHash.constFind(type); + if (it != m_typeInfoHash.constEnd()) + return it.value(); + bool cacheable = true; + TypeInfo result = createTypeInfoUncached(type, &cacheable); + if (cacheable) + m_typeInfoHash.insert(type, result); + return result; +} + +void BuilderPrivate::addTypeDef(const CXCursor &cursor, const CXType &cxType) +{ + const QString target = getCursorSpelling(cursor); + auto item = std::make_shared<_TypeDefModelItem>(m_model, target); + setFileName(cursor, item.get()); + item->setType(createTypeInfo(cxType)); + item->setScope(m_scope); + m_scopeStack.back()->addTypeDef(item); +} + +ClassModelItem BuilderPrivate::currentTemplateClass() const +{ + for (auto i = m_scopeStack.size() - 1; i >= 0; --i) { + auto klass = std::dynamic_pointer_cast<_ClassModelItem>(m_scopeStack.at(i)); + if (klass && klass->isTemplate()) + return klass; + } + return {}; +} + +void BuilderPrivate::startTemplateTypeAlias(const CXCursor &cursor) +{ + const QString target = getCursorSpelling(cursor); + m_currentTemplateTypeAlias.reset(new _TemplateTypeAliasModelItem(m_model, target)); + setFileName(cursor, m_currentTemplateTypeAlias.get()); + m_currentTemplateTypeAlias->setScope(m_scope); +} + +void BuilderPrivate::endTemplateTypeAlias(const CXCursor &typeAliasCursor) +{ + CXType type = clang_getTypedefDeclUnderlyingType(typeAliasCursor); + // Usually "<elaborated>std::list<T>" or "<unexposed>Container1<T>", + // as obtained with parser of PYSIDE-323 + if (type.kind == CXType_Unexposed || type.kind == CXType_Elaborated) { + m_currentTemplateTypeAlias->setType(createTypeInfo(type)); + m_scopeStack.back()->addTemplateTypeAlias(m_currentTemplateTypeAlias); + } + m_currentTemplateTypeAlias.reset(); +} + +// extract an expression from the cursor via source +// CXCursor_EnumConstantDecl, ParmDecl (a = Flag1 | Flag2) +QString BuilderPrivate::cursorValueExpression(BaseVisitor *bv, const CXCursor &cursor) +{ + const std::string_view snippet = bv->getCodeSnippet(cursor); + auto equalSign = snippet.find('='); + if (equalSign == std::string::npos) + return QString(); + ++equalSign; + QString result = QString::fromLocal8Bit(snippet.data() + equalSign, + qsizetype(snippet.size() - equalSign)); + // Fix a default expression as read from code. Simplify white space + result.remove(u'\r'); + return result.contains(u'"') ? result.trimmed() : result.simplified(); +} + +// Resolve a type (loop over aliases/typedefs), for example for base classes +// Note: TypeAliasTemplateDecl ("using QVector<T>=QList<T>") is automatically +// resolved by clang_getTypeDeclaration(), but it stops at +// TypeAliasDecl / TypedefDecl. + +struct TypeDeclaration +{ + CXType type; + CXCursor declaration; +}; + +static inline bool isTypeAliasDecl(const CXCursor &cursor) +{ + const auto kind = clang_getCursorKind(cursor); + return kind == CXCursor_TypeAliasDecl || kind == CXCursor_TypedefDecl; +} + +static TypeDeclaration resolveBaseClassType(CXType type) +{ + CXCursor decl = clang_getTypeDeclaration(type); + auto resolvedType = clang_getCursorType(decl); + if (resolvedType.kind != CXType_Invalid && resolvedType.kind != type.kind) + type = resolvedType; + while (isTypeAliasDecl(decl)) { + type = clang_getTypedefDeclUnderlyingType(decl); + decl = clang_getTypeDeclaration(type); + } + return {type, decl}; +} + +// Note: Return the baseclass for cursors like CXCursor_CXXBaseSpecifier, +// where the cursor spelling has "struct baseClass". +std::pair<QString, ClassModelItem> BuilderPrivate::getBaseClass(CXType type) const +{ + const auto decl = resolveBaseClassType(type); + // Note: spelling has "struct baseClass", use type + QString baseClassName = getTypeName(decl.type); + if (baseClassName.startsWith(u"std::")) // Simplify "std::" types + baseClassName = createTypeInfo(decl.type).toString(); + + auto it = m_cursorClassHash.constFind(decl.declaration); + // Not found: Set unqualified name. This happens in cases like + // "class X : public std::list<...>", "template<class T> class Foo : public T" + // and standard types like true_type, false_type. + if (it == m_cursorClassHash.constEnd()) + return {baseClassName, {}}; + + // Completely qualify the class name by looking it up and taking its scope + // plus the actual baseClass stripped off any scopes. Consider: + // namespace std { + // template <class T> class vector {}; + // namespace n { + // class Foo : public vector<int> {}; + // } + // } + // should have "std::vector<int>" as base class (whereas the type of the base class is + // "std::vector<T>"). + const QStringList &baseScope = it.value()->scope(); + if (!baseScope.isEmpty()) { + const int lastSep = baseClassName.lastIndexOf(u"::"_s); + if (lastSep >= 0) + baseClassName.remove(0, lastSep + u"::"_s.size()); + baseClassName.prepend(u"::"_s); + baseClassName.prepend(baseScope.join(u"::"_s)); + } + return {baseClassName, it.value()}; +} + +// Add a base class to the current class from CXCursor_CXXBaseSpecifier +void BuilderPrivate::addBaseClass(const CXCursor &cursor) +{ + Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_CXXBaseSpecifier); + const auto access = accessPolicy(clang_getCXXAccessSpecifier(cursor)); + const auto baseClass = getBaseClass(clang_getCursorType(cursor)); + m_currentClass->addBaseClass({baseClass.first, baseClass.second, access}); +} + +void BuilderPrivate::setFileName(const CXCursor &cursor, _CodeModelItem *item) +{ + const SourceRange range = getCursorRange(cursor); + QString file = m_baseVisitor->getFileName(range.first.file); + if (!file.isEmpty()) { // Has been observed to be 0 for invalid locations + item->setFileName(QDir::cleanPath(file)); + item->setStartPosition(int(range.first.line), int(range.first.column)); + item->setEndPosition(int(range.second.line), int(range.second.column)); + } +} + +Builder::Builder() +{ + d = new BuilderPrivate(this); +} + +Builder::~Builder() +{ + delete d; +} + +static QString baseName(QString path) +{ + qsizetype lastSlash = path.lastIndexOf(u'/'); +#ifdef Q_OS_WIN + if (lastSlash < 0) + lastSlash = path.lastIndexOf(u'\\'); +#endif + if (lastSlash > 0) + path.remove(0, lastSlash + 1); + return path; +} + +const char * BuilderPrivate::specialSystemHeaderReason(BuilderPrivate::SpecialSystemHeader sh) +{ + static const QHash<SpecialSystemHeader, const char *> mapping { + {SpecialSystemHeader::OpenGL, "OpenGL"}, + {SpecialSystemHeader::Types, "types"}, + {SpecialSystemHeader::WhiteListed, "white listed"}, + {SpecialSystemHeader::WhiteListedPath, "white listed path"} + }; + return mapping.value(sh, ""); +} + +bool BuilderPrivate::visitHeader(const QString &fileName) const +{ + auto it = m_systemHeaders.find(fileName); + if (it == m_systemHeaders.end()) { + it = m_systemHeaders.insert(fileName, specialSystemHeader(fileName)); + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) { + const QString &name = QDir::toNativeSeparators(fileName); + if (it.value() == SpecialSystemHeader::None) { + qCInfo(lcShiboken, "Skipping system header %s", qPrintable(name)); + } else { + qCInfo(lcShiboken, "Parsing system header %s (%s)", + qPrintable(name), specialSystemHeaderReason(it.value())); + } + } + } + return it.value() != SpecialSystemHeader::None; +} + +BuilderPrivate::SpecialSystemHeader + BuilderPrivate::specialSystemHeader(const QString &fileName) const +{ + // Resolve OpenGL typedefs although the header is considered a system header. + const QString baseName = clang::baseName(fileName); + if (baseName == u"gl.h" + || baseName == u"gl2.h" + || baseName == u"gl3.h" + || baseName == u"gl31.h" + || baseName == u"gl32.h" + || baseName == u"stdint.h" // Windows: int32_t, uint32_t + || baseName == u"stddef.h") { // size_t` + return SpecialSystemHeader::OpenGL; + } + + switch (clang::platform()) { + case Platform::Unix: + if (fileName == u"/usr/include/stdlib.h" + || baseName == u"types.h" + || baseName == u"stdint-intn.h" // int32_t + || baseName == u"stdint-uintn.h") { // uint32_t + return SpecialSystemHeader::Types; + } + break; + case Platform::macOS: + // Parse the following system headers to get the correct typdefs for types like + // int32_t, which are used in the macOS implementation of OpenGL framework. + // They are installed under /Applications/Xcode.app/Contents/Developer/Platforms... + if (baseName == u"gltypes.h" + || fileName.contains(u"/usr/include/_types") + || fileName.contains(u"/usr/include/sys/_types")) { + return SpecialSystemHeader::Types; + } + break; + default: + break; + } + + // When building against system Qt (as it happens with yocto / Boot2Qt), the Qt headers are + // considered system headers by clang_Location_isInSystemHeader, and shiboken will not + // process them. We need to explicitly process them by checking against the list of + // include paths that were passed to shiboken's --force-process-system-include-paths option + // or specified via the <system-include> xml tag. + if (m_forceProcessSystemIncludes.contains(baseName)) + return SpecialSystemHeader::WhiteListed; + + if (std::any_of(m_forceProcessSystemIncludePaths.cbegin(), + m_forceProcessSystemIncludePaths.cend(), + [fileName](const QString &p) { return fileName.startsWith(p); })) { + return SpecialSystemHeader::WhiteListedPath; + } + + return SpecialSystemHeader::None; +} + +bool Builder::visitLocation(const QString &fileName, LocationType locationType) const +{ + return locationType != LocationType::System || d->visitHeader(fileName); +} + +void Builder::setForceProcessSystemIncludes(const QStringList &systemIncludes) +{ + for (const auto &i : systemIncludes) { + QFileInfo fi(i); + if (fi.exists() && fi.isDir()) + d->m_forceProcessSystemIncludePaths.append(i); + else + d->m_forceProcessSystemIncludes.append(i); + } +} + +FileModelItem Builder::dom() const +{ + Q_ASSERT(!d->m_scopeStack.isEmpty()); + auto rootScope = d->m_scopeStack.constFirst(); + rootScope->purgeClassDeclarations(); + return std::dynamic_pointer_cast<_FileModelItem>(rootScope); +} + +static QString msgOutOfOrder(const CXCursor &cursor, const char *expectedScope) +{ + return getCursorKindName(cursor.kind) + u' ' + + getCursorSpelling(cursor) + u" encountered outside "_s + + QLatin1StringView(expectedScope) + u'.'; +} + +static CodeModel::ClassType codeModelClassTypeFromCursor(CXCursorKind kind) +{ + CodeModel::ClassType result = CodeModel::Class; + if (kind == CXCursor_UnionDecl) + result = CodeModel::Union; + else if (kind == CXCursor_StructDecl) + result = CodeModel::Struct; + return result; +} + +static NamespaceType namespaceType(const CXCursor &cursor) +{ + if (clang_Cursor_isAnonymous(cursor)) + return NamespaceType::Anonymous; +#if CINDEX_VERSION_MAJOR > 0 || CINDEX_VERSION_MINOR >= 59 + if (clang_Cursor_isInlineNamespace(cursor)) + return NamespaceType::Inline; +#endif + return NamespaceType::Default; +} + +static QString enumType(const CXCursor &cursor) +{ + QString name = getCursorSpelling(cursor); // "enum Foo { v1, v2 };" + if (name.contains(u"unnamed enum")) // Clang 16.0 + return {}; + if (name.isEmpty()) { + // PYSIDE-1228: For "typedef enum { v1, v2 } Foo;", type will return + // "Foo" as expected. Care must be taken to exclude real anonymous enums. + name = getTypeName(clang_getCursorType(cursor)); + if (name.contains(u"(unnamed") // Clang 12.0.1 + || name.contains(u"(anonymous")) { // earlier + name.clear(); + } + } + return name; +} + +BaseVisitor::StartTokenResult Builder::startToken(const CXCursor &cursor) +{ + switch (cursor.kind) { + case CXCursor_CXXAccessSpecifier: + d->m_currentFunctionType = CodeModel::Normal; + break; + case CXCursor_AnnotateAttr: { + const QString annotation = getCursorSpelling(cursor); + if (annotation == u"qt_slot") + d->m_currentFunctionType = CodeModel::Slot; + else if (annotation == u"qt_signal") + d->m_currentFunctionType = CodeModel::Signal; + else + d->m_currentFunctionType = CodeModel::Normal; + } + break; + case CXCursor_CXXBaseSpecifier: + if (!d->m_currentClass) { + const Diagnostic d(msgOutOfOrder(cursor, "class"), cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + d->addBaseClass(cursor); + break; + case CXCursor_ClassDecl: + case CXCursor_UnionDecl: + case CXCursor_StructDecl: + if (d->m_withinFriendDecl || clang_isCursorDefinition(cursor) == 0 + || !d->addClass(cursor, codeModelClassTypeFromCursor(cursor.kind))) { + return Skip; + } + break; + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + if (d->m_withinFriendDecl || clang_isCursorDefinition(cursor) == 0 + || !d->addClass(cursor, CodeModel::Class)) { + return Skip; + } + d->m_currentClass->setName(d->m_currentClass->name() + "<>"_L1); + d->m_scope.back() += "<>"_L1; + break; + case CXCursor_EnumDecl: { + QString name = enumType(cursor); + EnumKind kind = CEnum; + if (name.isEmpty()) { + kind = AnonymousEnum; + name = "enum_"_L1 + QString::number(++d->m_anonymousEnumCount); +#if !CLANG_NO_ENUMDECL_ISSCOPED + } else if (clang_EnumDecl_isScoped(cursor) != 0) { +#else + } else if (clang_EnumDecl_isScoped4(this, cursor) != 0) { +#endif + kind = EnumClass; + } + d->m_currentEnum.reset(new _EnumModelItem(d->m_model, name)); + d->setFileName(cursor, d->m_currentEnum.get()); + d->m_currentEnum->setScope(d->m_scope); + d->m_currentEnum->setEnumKind(kind); + if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated) + d->m_currentEnum->setDeprecated(true); + const auto enumType = fullyResolveType(clang_getEnumDeclIntegerType(cursor)); + d->m_currentEnum->setSigned(isSigned(enumType.kind)); + d->m_currentEnum->setUnderlyingType(getTypeName(enumType)); + if (std::dynamic_pointer_cast<_ClassModelItem>(d->m_scopeStack.back())) + d->m_currentEnum->setAccessPolicy(accessPolicy(clang_getCXXAccessSpecifier(cursor))); + } + break; + case CXCursor_EnumConstantDecl: { + const QString name = getCursorSpelling(cursor); + if (!d->m_currentEnum) { + const Diagnostic d(msgOutOfOrder(cursor, "enum"), cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + EnumValue enumValue; + if (d->m_currentEnum->isSigned()) + enumValue.setValue(clang_getEnumConstantDeclValue(cursor)); + else + enumValue.setUnsignedValue(clang_getEnumConstantDeclUnsignedValue(cursor)); + auto enumConstant = std::make_shared<_EnumeratorModelItem>(d->m_model, name); + enumConstant->setStringValue(d->cursorValueExpression(this, cursor)); + enumConstant->setValue(enumValue); + if (clang_getCursorAvailability(cursor) == CXAvailability_Deprecated) + enumConstant->setDeprecated(true); + d->m_currentEnum->addEnumerator(enumConstant); + } + break; + case CXCursor_VarDecl: + // static class members are seen as CXCursor_VarDecl + if (isClassOrNamespaceCursor(clang_getCursorSemanticParent(cursor))) { + d->addField(cursor); + d->m_currentField->setStatic(true); + } + break; + case CXCursor_FieldDecl: + d->addField(cursor); + break; + case CXCursor_FriendDecl: + d->m_withinFriendDecl = true; + break; + case CXCursor_CompoundStmt: // Function bodies + return Skip; + case CXCursor_Constructor: + case CXCursor_Destructor: // Note: Also use clang_CXXConstructor_is..Constructor? + case CXCursor_CXXMethod: + case CXCursor_ConversionFunction: + // Member functions of other classes can be declared to be friends. + // Skip inline member functions outside class, only go by declarations inside class + if (d->m_withinFriendDecl || !withinClassDeclaration(cursor)) + return Skip; + d->m_currentFunction = d->createMemberFunction(cursor, false); + d->m_scopeStack.back()->addFunction(d->m_currentFunction); + break; + // Not fully supported, currently, seen as normal function + // Note: May appear inside class (member template) or outside (free template). + case CXCursor_FunctionTemplate: { + const CXCursor semParent = clang_getCursorSemanticParent(cursor); + if (isClassCursor(semParent)) { + if (semParent == clang_getCursorLexicalParent(cursor)) { + d->m_currentFunction = d->createMemberFunction(cursor, true); + d->m_scopeStack.back()->addFunction(d->m_currentFunction); + break; + } + return Skip; // inline member functions outside class + } + } + d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, true); + d->setFileName(cursor, d->m_currentFunction.get()); + d->m_scopeStack.back()->addFunction(d->m_currentFunction); + break; + case CXCursor_FunctionDecl: + // Free functions or functions completely defined within "friend" (class + // operators). Note: CXTranslationUnit_SkipFunctionBodies must be off for + // clang_isCursorDefinition() to work here. + if (!d->m_withinFriendDecl || clang_isCursorDefinition(cursor) != 0) { + auto scope = d->m_scopeStack.size() - 1; // enclosing class + if (d->m_withinFriendDecl) { + // Friend declaration: go back to namespace or file scope. + for (--scope; d->m_scopeStack.at(scope)->kind() == _CodeModelItem::Kind_Class; --scope) { + } + } + d->m_currentFunction = d->createFunction(cursor, CodeModel::Normal, false); + d->m_currentFunction->setHiddenFriend(d->m_withinFriendDecl); + d->m_scopeStack.at(scope)->addFunction(d->m_currentFunction); + } + break; + case CXCursor_Namespace: { + const auto type = namespaceType(cursor); + if (type == NamespaceType::Anonymous) + return Skip; + const QString name = getCursorSpelling(cursor); + const auto parentNamespaceItem = std::dynamic_pointer_cast<_NamespaceModelItem>(d->m_scopeStack.back()); + if (!parentNamespaceItem) { + const QString message = msgOutOfOrder(cursor, "namespace") + + u" (current scope: "_s + d->m_scopeStack.back()->name() + u')'; + const Diagnostic d(message, cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + // Treat namespaces separately to allow for extending namespaces + // in subsequent modules. + NamespaceModelItem namespaceItem = parentNamespaceItem->findNamespace(name); + namespaceItem.reset(new _NamespaceModelItem(d->m_model, name)); + d->setFileName(cursor, namespaceItem.get()); + namespaceItem->setScope(d->m_scope); + namespaceItem->setType(type); + parentNamespaceItem->addNamespace(namespaceItem); + d->pushScope(namespaceItem); + } + break; + case CXCursor_ParmDecl: + // Skip in case of nested CXCursor_ParmDecls in case one parameter is a function pointer + // and function pointer typedefs. + if (!d->m_currentArgument && d->m_currentFunction) { + const QString name = getCursorSpelling(cursor); + d->m_currentArgument.reset(new _ArgumentModelItem(d->m_model, name)); + const auto type = clang_getCursorType(cursor); + d->m_currentArgument->setScopeResolution(hasScopeResolution(type)); + d->m_currentArgument->setType(d->createTypeInfo(type)); + d->m_currentFunction->addArgument(d->m_currentArgument); + QString defaultValueExpression = d->cursorValueExpression(this, cursor); + if (!defaultValueExpression.isEmpty()) { + d->m_currentArgument->setDefaultValueExpression(defaultValueExpression); + d->m_currentArgument->setDefaultValue(true); + } + } else { + return Skip; + } + break; + case CXCursor_TemplateTypeParameter: + case CXCursor_NonTypeTemplateParameter: { + const TemplateParameterModelItem tItem = cursor.kind == CXCursor_TemplateTemplateParameter + ? d->createTemplateParameter(cursor) : d->createNonTypeTemplateParameter(cursor); + // Apply to function/member template? + if (d->m_currentFunction) { + d->m_currentFunction->setTemplateParameters(d->m_currentFunction->templateParameters() << tItem); + } else if (d->m_currentTemplateTypeAlias) { + d->m_currentTemplateTypeAlias->addTemplateParameter(tItem); + } else if (d->m_currentClass) { // Apply to class + const QString &tplParmName = tItem->name(); + if (Q_UNLIKELY(!insertTemplateParameterIntoClassName(tplParmName, d->m_currentClass) + || !insertTemplateParameterIntoClassName(tplParmName, &d->m_scope.back()))) { + const QString message = "Error inserting template parameter \""_L1 + tplParmName + + "\" into "_L1 + d->m_currentClass->name(); + const Diagnostic d(message, cursor, CXDiagnostic_Error); + qWarning() << d; + appendDiagnostic(d); + return Error; + } + d->m_currentClass->setTemplateParameters(d->m_currentClass->templateParameters() << tItem); + } + } + break; + case CXCursor_TypeAliasTemplateDecl: + d->startTemplateTypeAlias(cursor); + break; + case CXCursor_TypeAliasDecl: // May contain nested CXCursor_TemplateTypeParameter + if (!d->m_currentTemplateTypeAlias) { + const CXType type = clang_getCanonicalType(clang_getCursorType(cursor)); + if (type.kind > CXType_Unexposed) + d->addTypeDef(cursor, type); + return Skip; + } else { + d->endTemplateTypeAlias(cursor); + } + break; + case CXCursor_TypedefDecl: { + auto underlyingType = clang_getTypedefDeclUnderlyingType(cursor); + d->addTypeDef(cursor, underlyingType); + // For "typedef enum/struct {} Foo;", skip the enum/struct + // definition nested into the typedef (PYSIDE-1228). + if (underlyingType.kind == CXType_Elaborated) + return Skip; + } + break; + // Using declarations look as follows: + // 1) Normal, non-template case ("using QObject::parent"): UsingDeclaration, TypeRef + // 2) Simple template case ("using QList::append()"): UsingDeclaration, TypeRef "QList<T>" + // 3) Template case with parameters ("using QList<T>::append()"): + // UsingDeclaration, TemplateRef "QList", TypeRef "T" + case CXCursor_TemplateRef: + if (d->m_withinUsingDeclaration && d->m_usingTypeRef.isEmpty()) + d->m_usingTypeRef = getCursorSpelling(cursor); + break; + case CXCursor_TypeRef: + if (d->m_withinUsingDeclaration && d->m_usingTypeRef.isEmpty()) + d->m_usingTypeRef = d->getBaseClass(clang_getCursorType(cursor)).first; + break; + case CXCursor_CXXFinalAttr: + if (d->m_currentFunction) + d->m_currentFunction->setAttribute(FunctionAttribute::Final); + else if (d->m_currentClass) + d->m_currentClass->setFinal(true); + break; + case CXCursor_CXXOverrideAttr: + if (d->m_currentFunction) + d->m_currentFunction->setAttribute(FunctionAttribute::Override); + break; + case CXCursor_StaticAssert: + // Check for Q_PROPERTY() (see PySide6/global.h.in for an explanation + // how it is defined, and qdoc). + if (clang_isDeclaration(cursor.kind) && d->m_currentClass) { + auto snippet = getCodeSnippet(cursor); + const auto length = snippet.size(); + if (length > 12 && *snippet.rbegin() == ')' + && snippet.compare(0, 11, "Q_PROPERTY(") == 0) { + const QString qProperty = QString::fromUtf8(snippet.data() + 11, length - 12); + d->m_currentClass->addPropertyDeclaration(qProperty); + } + } + break; + // UsingDeclaration: consists of a TypeRef (base) and OverloadedDeclRef (member name) + case CXCursor_UsingDeclaration: + if (d->m_currentClass) + d->m_withinUsingDeclaration = true; + break; + case CXCursor_OverloadedDeclRef: + if (d->m_withinUsingDeclaration && !d->m_usingTypeRef.isEmpty()) { + QString member = getCursorSpelling(cursor); + if (member == d->m_currentClass->name()) + member = d->m_usingTypeRef; // Overloaded member is Constructor, use base + const auto ap = accessPolicy(clang_getCXXAccessSpecifier(cursor)); + d->m_currentClass->addUsingMember(d->m_usingTypeRef, member, ap); + } + break; + default: + break; + } + return BaseVisitor::Recurse; +} + +bool Builder::endToken(const CXCursor &cursor) +{ + switch (cursor.kind) { + case CXCursor_UnionDecl: + case CXCursor_ClassDecl: + case CXCursor_StructDecl: + case CXCursor_ClassTemplate: + case CXCursor_ClassTemplatePartialSpecialization: + d->popScope(); + // Continue in outer class after leaving inner class? + if (auto lastClass = std::dynamic_pointer_cast<_ClassModelItem>(d->m_scopeStack.back())) + d->m_currentClass = lastClass; + else + d->m_currentClass.reset(); + d->m_currentFunctionType = CodeModel::Normal; + break; + case CXCursor_EnumDecl: + if (d->m_currentEnum) + d->m_scopeStack.back()->addEnum(d->m_currentEnum); + d->m_currentEnum.reset(); + break; + case CXCursor_FriendDecl: + d->m_withinFriendDecl = false; + break; + case CXCursor_VarDecl: + case CXCursor_FieldDecl: + d->m_currentField.reset(); + break; + case CXCursor_Constructor: + d->qualifyConstructor(cursor); + if (d->m_currentFunction) { + d->m_currentFunction->_determineType(); + d->m_currentFunction.reset(); + } + break; + case CXCursor_Destructor: + case CXCursor_CXXMethod: + case CXCursor_FunctionDecl: + case CXCursor_FunctionTemplate: + if (d->m_currentFunction) { + d->m_currentFunction->_determineType(); + d->m_currentFunction.reset(); + } + break; + case CXCursor_ConversionFunction: + if (d->m_currentFunction) { + d->m_currentFunction->setFunctionType(CodeModel::ConversionOperator); + d->m_currentFunction.reset(); + } + break; + case CXCursor_Namespace: + d->popScope(); + break; + case CXCursor_ParmDecl: + d->m_currentArgument.reset(); + break; + case CXCursor_TypeAliasTemplateDecl: + d->m_currentTemplateTypeAlias.reset(); + break; + case CXCursor_UsingDeclaration: + d->m_withinUsingDeclaration = false; + d->m_usingTypeRef.clear(); + break; + default: + break; + } + return true; +} + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h new file mode 100644 index 000000000..b2ec6d304 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangbuilder.h @@ -0,0 +1,37 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGBUILDER_H +#define CLANGBUILDER_H + +#include "clangparser.h" + +#include <codemodel_fwd.h> + +namespace clang { + +class BuilderPrivate; + +class Builder : public BaseVisitor { +public: + Q_DISABLE_COPY_MOVE(Builder) + + Builder(); + ~Builder(); + + void setForceProcessSystemIncludes(const QStringList &systemIncludes); + + bool visitLocation(const QString &fileName, LocationType locationType) const override; + + StartTokenResult startToken(const CXCursor &cursor) override; + bool endToken(const CXCursor &cursor) override; + + FileModelItem dom() const; + +private: + BuilderPrivate *d; +}; + +} // namespace clang + +#endif // CLANGBUILDER_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp new file mode 100644 index 000000000..3c002da9c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.cpp @@ -0,0 +1,175 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clangdebugutils.h" +#include "clangutils.h" + +#include <QtCore/QDebug> +#include <QtCore/QString> + +#ifndef QT_NO_DEBUG_STREAM + +#ifdef Q_OS_WIN +const char pathSep = '\\'; +#else +const char pathSep = '/'; +#endif + +static const char *baseName(const char *fileName) +{ + const char *b = std::strrchr(fileName, pathSep); + return b ? b + 1 : fileName; +} + +QDebug operator<<(QDebug s, const CXString &cs) +{ + s << clang_getCString(cs); + return s; +} + +QDebug operator<<(QDebug s, CXCursorKind cursorKind) // Enum +{ + const CXString kindName = clang_getCursorKindSpelling(cursorKind); + s << kindName; + clang_disposeString(kindName); + return s; +} + +static const char *accessSpecsStrings[] +{ + // CX_CXXInvalidAccessSpecifier, CX_CXXPublic, CX_CXXProtected, CX_CXXPrivate + "invalid", "public", "protected", "private" +}; + +QDebug operator<<(QDebug s, CX_CXXAccessSpecifier ac) +{ + s << accessSpecsStrings[ac]; + return s; +} + +struct formatCXTypeName +{ + explicit formatCXTypeName(const CXType &type) : m_type(type) {} + + const CXType &m_type; +}; + +QDebug operator<<(QDebug debug, const formatCXTypeName &ft) +{ + CXString typeSpelling = clang_getTypeSpelling(ft.m_type); + debug << typeSpelling; + clang_disposeString(typeSpelling); + return debug; +} + +QDebug operator<<(QDebug debug, const CXType &type) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug.noquote(); + debug << "CXType("; + if (type.kind == CXType_Invalid) { + debug << "invalid)"; + return debug; + } + + debug << type.kind; + switch (type.kind) { + case CXType_Unexposed: + debug << " [unexposed]"; + break; + case CXType_Elaborated: + debug << " [elaborated]"; + break; + default: + break; + } + debug << ", " << formatCXTypeName(type) << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const CXCursor &cursor) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug.noquote(); + const CXCursorKind kind = clang_getCursorKind(cursor); + debug << "CXCursor("; + if (kind >= CXCursor_FirstInvalid && kind <= CXCursor_LastInvalid) { + debug << "invalid)"; + return debug; + } + + const QString cursorSpelling = clang::getCursorSpelling(cursor); + debug << '"' << cursorSpelling << '"'; + CXString cursorDisplay = clang_getCursorDisplayName(cursor); + if (const char *dpy = clang_getCString(cursorDisplay)) { + const QString display = QString::fromUtf8(dpy); + if (display != cursorSpelling) + debug << ", display=\"" << dpy << '"'; + } + clang_disposeString(cursorDisplay); + + debug << ", kind=" << kind; + + const CXType type = clang_getCursorType(cursor); + switch (kind) { + case CXCursor_CXXAccessSpecifier: + debug << ", " << clang_getCXXAccessSpecifier(cursor); + break; + case CXCursor_CXXBaseSpecifier: + debug << ", inherits=\"" << clang::getCursorSpelling(clang_getTypeDeclaration(type)) << '"'; + break; + case CXCursor_CXXMethod: + case CXCursor_FunctionDecl: + case CXCursor_ConversionFunction: + debug << ", result type=\"" + << formatCXTypeName(clang_getCursorResultType(cursor)) << '"'; + break; + case CXCursor_TypedefDecl: + debug << ", underlyingType=\"" + << formatCXTypeName(clang_getTypedefDeclUnderlyingType(cursor)) << '"'; + break; + default: + break; + } + + debug << ", type=\"" << formatCXTypeName(type) << '"'; + if (clang_Cursor_hasAttrs(cursor)) + debug << ", [attrs]"; + + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug s, const CXSourceLocation &location) +{ + QDebugStateSaver saver(s); + s.nospace(); + CXFile file; // void * + unsigned line; + unsigned column; + unsigned offset; + clang_getExpansionLocation(location, &file, &line, &column, &offset); + const CXString cxFileName = clang_getFileName(file); + // Has been observed to be 0 for invalid locations + if (const char *cFileName = clang_getCString(cxFileName)) + s << baseName(cFileName) << ':'; + s << line << ':' << column; + clang_disposeString(cxFileName); + return s; +} + +QDebug operator<<(QDebug s, const std::string_view &v) +{ + QDebugStateSaver saver(s); + s.nospace(); + s.noquote(); + s << '"'; + for (auto c : v) + s << c; + s << '"'; + return s; +} + +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h new file mode 100644 index 000000000..7aac8a575 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangdebugutils.h @@ -0,0 +1,26 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGDEBUGUTILS_H +#define CLANGDEBUGUTILS_H + +#include <QtCore/qtclasshelpermacros.h> + +#include <clang-c/Index.h> + +#include <string_view> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QString) + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug s, const CXString &cs); +QDebug operator<<(QDebug s, CXCursorKind cursorKind); +QDebug operator<<(QDebug s, CX_CXXAccessSpecifier ac); +QDebug operator<<(QDebug s, const CXType &t); +QDebug operator<<(QDebug s, const CXCursor &cursor); +QDebug operator<<(QDebug s, const CXSourceLocation &location); +QDebug operator<<(QDebug s, const std::string_view &v); // for code snippets +#endif // !QT_NO_DEBUG_STREAM + +#endif // CLANGDEBUGUTILS_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp new file mode 100644 index 000000000..6c0cf3ae2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.cpp @@ -0,0 +1,323 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clangparser.h" +#include "clangutils.h" +#include "clangdebugutils.h" +#include "compilersupport.h" + +#include <QtCore/QByteArrayList> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QScopedArrayPointer> +#include <QtCore/QString> + +using namespace Qt::StringLiterals; + +namespace clang { + +QString SourceFileCache::getFileName(CXFile file) +{ + auto it = m_fileNameCache.find(file); + if (it == m_fileNameCache.end()) + it = m_fileNameCache.insert(file, clang::getFileName(file)); + return it.value(); +} + +std::string_view SourceFileCache::getCodeSnippet(const CXCursor &cursor, + QString *errorMessage) +{ + static const char empty[] = ""; + + if (errorMessage) + errorMessage->clear(); + + const SourceRange range = getCursorRange(cursor); + // Quick check for equal locations: Frequently happens if the code is + // the result of a macro expansion + if (range.first == range.second) + return std::string_view(empty, 0); + + if (range.first.file != range.second.file) { + if (errorMessage) + *errorMessage = "Range spans several files"_L1; + return std::string_view(empty, 0); + } + + auto it = m_fileBufferCache.find(range.first.file); + if (it == m_fileBufferCache.end()) { + const QString fileName = getFileName(range.first.file); + if (fileName.isEmpty()) { + if (errorMessage) + *errorMessage = "Range has no file"_L1; + return std::string_view(empty, 0); + } + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + if (errorMessage) { + QTextStream str(errorMessage); + str << "Cannot open \"" << QDir::toNativeSeparators(fileName) + << "\": " << file.errorString(); + } + return std::string_view(empty, 0); + } + it = m_fileBufferCache.insert(range.first.file, file.readAll()); + } + + const unsigned pos = range.first.offset; + const unsigned end = range.second.offset; + Q_ASSERT(end > pos); + const QByteArray &contents = it.value(); + if (end >= unsigned(contents.size())) { + if (errorMessage) { + QTextStream str(errorMessage); + str << "Range end " << end << " is above size of \"" + << QDir::toNativeSeparators(getFileName(range.first.file)) + << "\" (" << contents.size() << ')'; + } + return std::string_view(empty, 0); + } + + return std::string_view(contents.constData() + pos, end - pos); +} + +BaseVisitor::BaseVisitor() = default; +BaseVisitor::~BaseVisitor() = default; + +bool BaseVisitor::visitLocation(const QString &, LocationType locationType) const +{ + return locationType != LocationType::System; +} + +BaseVisitor::StartTokenResult BaseVisitor::cbHandleStartToken(const CXCursor &cursor) +{ + switch (cursor.kind) { + default: + break; + } + + return startToken(cursor); +} + +bool BaseVisitor::cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult) +{ + const bool result = startResult != Recurse || endToken(cursor); + switch (cursor.kind) { + default: + break; + } + + return result; +} + +std::string_view BaseVisitor::getCodeSnippet(const CXCursor &cursor) +{ + QString errorMessage; + const std::string_view result = m_fileCache.getCodeSnippet(cursor, &errorMessage); + if (result.empty() && !errorMessage.isEmpty()) { + QString message; + QTextStream str(&message); + str << "Unable to retrieve code snippet \"" << getCursorSpelling(cursor) + << "\": " << errorMessage; + appendDiagnostic(Diagnostic(message, cursor, CXDiagnostic_Error)); + } + return result; +} + +bool BaseVisitor::_handleVisitLocation(const CXSourceLocation &location) +{ + CXFile cxFile; // void * + unsigned line; + unsigned column; + unsigned offset; + clang_getExpansionLocation(location, &cxFile, &line, &column, &offset); + + if (cxFile == m_currentCxFile) // Same file? + return m_visitCurrent; + + const QString fileName = getFileName(cxFile); + + LocationType locationType = LocationType::Unknown; + if (!fileName.isEmpty()) { + if (clang_Location_isFromMainFile(location) != 0) + locationType = LocationType::Main; + else if (clang_Location_isInSystemHeader(location) != 0) + locationType = LocationType::System; + else + locationType = LocationType::Other; + } + + m_currentCxFile = cxFile; + m_visitCurrent = visitLocation(fileName, locationType); + return m_visitCurrent; +} + +QString BaseVisitor::getCodeSnippetString(const CXCursor &cursor) +{ + const std::string_view result = getCodeSnippet(cursor); + return result.empty() + ? QString() + : QString::fromUtf8(result.data(), qsizetype(result.size())); +} + +static CXChildVisitResult + visitorCallback(CXCursor cursor, CXCursor /* parent */, CXClientData clientData) +{ + auto *bv = reinterpret_cast<BaseVisitor *>(clientData); + + const CXSourceLocation location = clang_getCursorLocation(cursor); + if (!bv->_handleVisitLocation(location)) + return CXChildVisit_Continue; + + const BaseVisitor::StartTokenResult startResult = bv->cbHandleStartToken(cursor); + switch (startResult) { + case clang::BaseVisitor::Error: + return CXChildVisit_Break; + case clang::BaseVisitor::Skip: + break; + case clang::BaseVisitor::Recurse: + clang_visitChildren(cursor, visitorCallback, clientData); + break; + } + + if (!bv->cbHandleEndToken(cursor, startResult)) + return CXChildVisit_Break; + + return CXChildVisit_Continue; +} + +BaseVisitor::Diagnostics BaseVisitor::diagnostics() const +{ + return m_diagnostics; +} + +void BaseVisitor::setDiagnostics(const Diagnostics &d) +{ + m_diagnostics = d; +} + +void BaseVisitor::appendDiagnostic(const Diagnostic &d) +{ + m_diagnostics.append(d); +} + +static inline const char **byteArrayListToFlatArgV(const QByteArrayList &bl) +{ + const char **result = new const char *[bl.size() + 1]; + result[bl.size()] = nullptr; + std::transform(bl.cbegin(), bl.cend(), result, + [] (const QByteArray &a) { return a.constData(); }); + return result; +} + +static QByteArray msgCreateTranslationUnit(const QByteArrayList &clangArgs, unsigned flags) +{ + QByteArray result = "clang_parseTranslationUnit2(0x"; + result += QByteArray::number(flags, 16); + const auto count = clangArgs.size(); + result += ", cmd[" + QByteArray::number(count) + "]="; + for (qsizetype i = 0; i < count; ++i) { + const QByteArray &arg = clangArgs.at(i); + if (i) + result += ' '; + const bool quote = arg.contains(' ') || arg.contains('('); + if (quote) + result += '"'; + result += arg; + if (quote) + result += '"'; + } + result += ')'; + return result; +} + +static CXTranslationUnit createTranslationUnit(CXIndex index, + const QByteArrayList &args, + bool addCompilerSupportArguments, + LanguageLevel level, + unsigned flags = 0) +{ + // courtesy qdoc + const unsigned defaultFlags = CXTranslationUnit_Incomplete; + + static const QByteArrayList defaultArgs = { +#ifndef Q_OS_WIN + "-fPIC", +#endif +#ifdef Q_OS_MACOS + "-Wno-expansion-to-defined", // Workaround for warnings in Darwin stdlib, see + // https://github.com/darlinghq/darling/issues/204 +#endif + "-Wno-constant-logical-operand", + "-x", + "c++" // Treat .h as C++, not C + }; + + QByteArrayList clangArgs; + if (addCompilerSupportArguments) { + clangArgs += emulatedCompilerOptions(level); + clangArgs += defaultArgs; + } + clangArgs += detectVulkan(); + clangArgs += args; + QScopedArrayPointer<const char *> argv(byteArrayListToFlatArgV(clangArgs)); + qDebug().noquote().nospace() << msgCreateTranslationUnit(clangArgs, flags); + + CXTranslationUnit tu; + CXErrorCode err = clang_parseTranslationUnit2(index, nullptr, argv.data(), + clangArgs.size(), nullptr, 0, + defaultFlags | flags, &tu); + if (err || !tu) { + qWarning().noquote().nospace() << "Could not parse " + << clangArgs.constLast().constData() << ", error code: " << err; + return nullptr; + } + return tu; +} + +/* clangFlags are flags to clang_parseTranslationUnit2() such as + * CXTranslationUnit_KeepGoing (from CINDEX_VERSION_MAJOR/CINDEX_VERSION_MINOR 0.35) + */ + +bool parse(const QByteArrayList &clangArgs, bool addCompilerSupportArguments, + LanguageLevel level, unsigned clangFlags, BaseVisitor &bv) +{ + CXIndex index = clang_createIndex(0 /* excludeDeclarationsFromPCH */, + 1 /* displayDiagnostics */); + if (!index) { + qWarning() << "clang_createIndex() failed!"; + return false; + } + + CXTranslationUnit translationUnit = + createTranslationUnit(index, clangArgs, addCompilerSupportArguments, + level, clangFlags); + if (!translationUnit) + return false; + + CXCursor rootCursor = clang_getTranslationUnitCursor(translationUnit); + + clang_visitChildren(rootCursor, visitorCallback, reinterpret_cast<CXClientData>(&bv)); + + QList<Diagnostic> diagnostics = getDiagnostics(translationUnit); + diagnostics.append(bv.diagnostics()); + bv.setDiagnostics(diagnostics); + + const bool ok = maxSeverity(diagnostics) < CXDiagnostic_Error; + if (!ok) { + QDebug debug = qWarning(); + debug.noquote(); + debug.nospace(); + debug << "Errors in " + << QDir::toNativeSeparators(QFile::decodeName(clangArgs.constLast())) << ":\n"; + for (const Diagnostic &diagnostic : std::as_const(diagnostics)) + debug << diagnostic << '\n'; + } + + clang_disposeTranslationUnit(translationUnit); + clang_disposeIndex(index); + return ok; +} + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangparser.h b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h new file mode 100644 index 000000000..22e0a50cd --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangparser.h @@ -0,0 +1,88 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGPARSER_H +#define CLANGPARSER_H + +#include <clang-c/Index.h> + +#include <QtCore/QByteArrayList> +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QList> + +#include <string_view> +#include <utility> + +enum class LanguageLevel; + +namespace clang { + +struct Diagnostic; + +class SourceFileCache { +public: + std::string_view getCodeSnippet(const CXCursor &cursor, QString *errorMessage = nullptr); + QString getFileName(CXFile file); + +private: + using FileBufferCache = QHash<CXFile, QByteArray>; + using FileNameCache = QHash<CXFile, QString>; + + FileBufferCache m_fileBufferCache; + FileNameCache m_fileNameCache; +}; + +enum class LocationType +{ + Main, // Main header parsed for bindings + Other, // A header parsed for bindings + System, // A system header + Unknown // Clang internal +}; + +class BaseVisitor { + Q_DISABLE_COPY_MOVE(BaseVisitor) +public: + using Diagnostics = QList<Diagnostic>; + + enum StartTokenResult { Error, Skip, Recurse }; + + BaseVisitor(); + virtual ~BaseVisitor(); + + // Whether location should be visited. + // defaults to clang_Location_isFromMainFile() + virtual bool visitLocation(const QString &fileName, LocationType locationType) const; + + virtual StartTokenResult startToken(const CXCursor &cursor) = 0; + virtual bool endToken(const CXCursor &cursor) = 0; + + StartTokenResult cbHandleStartToken(const CXCursor &cursor); + bool cbHandleEndToken(const CXCursor &cursor, StartTokenResult startResult); + + QString getFileName(CXFile file) { return m_fileCache.getFileName(file); } + std::string_view getCodeSnippet(const CXCursor &cursor); + QString getCodeSnippetString(const CXCursor &cursor); + + Diagnostics diagnostics() const; + void setDiagnostics(const Diagnostics &d); + void appendDiagnostic(const Diagnostic &d); + + // For usage by the parser + bool _handleVisitLocation( const CXSourceLocation &location); + +private: + SourceFileCache m_fileCache; + Diagnostics m_diagnostics; + CXFile m_currentCxFile{}; + bool m_visitCurrent = true; +}; + +bool parse(const QByteArrayList &clangArgs, + bool addCompilerSupportArguments, + LanguageLevel level, unsigned clangFlags, BaseVisitor &ctx); + +} // namespace clang + +#endif // !CLANGPARSER_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp new file mode 100644 index 000000000..1651e09ec --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.cpp @@ -0,0 +1,320 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "clangutils.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QHashFunctions> +#include <QtCore/QProcess> + +#include <string_view> + +bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept +{ + return c1.kind == c2.kind + && c1.xdata == c2.xdata + && std::equal(c1.data, c1.data + sizeof(c1.data) / sizeof(c1.data[0]), c2.data); +} + +size_t qHash(const CXCursor &c, size_t seed) noexcept +{ + return qHashMulti(seed, c.kind, c.xdata, c.data[0], c.data[1], c.data[2]); +} + +bool operator==(const CXType &t1, const CXType &t2) noexcept +{ + return t1.kind == t2.kind && t1.data[0] == t2.data[0] + && t1.data[1] == t2.data[1]; +} + +size_t qHash(const CXType &ct, size_t seed) noexcept +{ + return qHashMulti(seed, ct.kind, ct.data[0], ct.data[1]); +} + +namespace clang { + +SourceLocation getExpansionLocation(const CXSourceLocation &location) +{ + SourceLocation result; + clang_getExpansionLocation(location, &result.file, &result.line, &result.column, &result.offset); + return result; +} + +QString getFileName(CXFile file) +{ + QString result; + const CXString cxFileName = clang_getFileName(file); + // Has been observed to be 0 for invalid locations + if (const char *cFileName = clang_getCString(cxFileName)) + result = QString::fromUtf8(cFileName); + clang_disposeString(cxFileName); + return result; +} + +SourceLocation getCursorLocation(const CXCursor &cursor) +{ + const CXSourceRange extent = clang_getCursorExtent(cursor); + return getExpansionLocation(clang_getRangeStart(extent)); +} + +CXString getFileNameFromLocation(const CXSourceLocation &location) +{ + CXFile file; + unsigned line; + unsigned column; + unsigned offset; + clang_getExpansionLocation(location, &file, &line, &column, &offset); + return clang_getFileName(file); +} + +SourceRange getCursorRange(const CXCursor &cursor) +{ + const CXSourceRange extent = clang_getCursorExtent(cursor); + return std::make_pair(getExpansionLocation(clang_getRangeStart(extent)), + getExpansionLocation(clang_getRangeEnd(extent))); +} + +QString getCursorKindName(CXCursorKind cursorKind) +{ + CXString kindName = clang_getCursorKindSpelling(cursorKind); + const QString result = QString::fromUtf8(clang_getCString(kindName)); + clang_disposeString(kindName); + return result; +} + +QString getCursorSpelling(const CXCursor &cursor) +{ + CXString cursorSpelling = clang_getCursorSpelling(cursor); + const QString result = QString::fromUtf8(clang_getCString(cursorSpelling)); + clang_disposeString(cursorSpelling); + return result; +} + +QString getCursorDisplayName(const CXCursor &cursor) +{ + CXString displayName = clang_getCursorDisplayName(cursor); + const QString result = QString::fromUtf8(clang_getCString(displayName)); + clang_disposeString(displayName); + return result; +} + +static inline bool isBuiltinType(CXTypeKind kind) +{ + return kind >= CXType_FirstBuiltin && kind <= CXType_LastBuiltin; +} + +// Resolve elaborated types occurring with clang 16 +static CXType resolveElaboratedType(const CXType &type) +{ + if (!isBuiltinType(type.kind)) { + CXCursor decl = clang_getTypeDeclaration(type); + auto resolvedType = clang_getCursorType(decl); + if (resolvedType.kind != CXType_Invalid && resolvedType.kind != type.kind) + return resolvedType; + } + return type; +} + +// Resolve typedefs +static CXType resolveTypedef(const CXType &type) +{ + auto result = type; + while (result.kind == CXType_Typedef) { + auto decl = clang_getTypeDeclaration(result); + auto resolved = clang_getTypedefDeclUnderlyingType(decl); + if (resolved.kind == CXType_Invalid) + break; + result = resolved; + } + return result; +} + +// Fully resolve a type from elaborated & typedefs +CXType fullyResolveType(const CXType &type) +{ + return resolveTypedef(resolveElaboratedType(type)); +} + +QString getTypeName(const CXType &type) +{ + CXString typeSpelling = clang_getTypeSpelling(type); + const QString result = QString::fromUtf8(clang_getCString(typeSpelling)); + clang_disposeString(typeSpelling); + return result; +} + +// Quick check for "::Type" +bool hasScopeResolution(const CXType &type) +{ + CXString typeSpelling = clang_getTypeSpelling(type); + std::string_view spelling = clang_getCString(typeSpelling); + const bool result = spelling.compare(0, 2, "::") == 0 + || spelling.find(" ::") != std::string::npos; + clang_disposeString(typeSpelling); + return result; +} + +// Resolve elaborated types occurring with clang 16 +QString getResolvedTypeName(const CXType &type) +{ + return getTypeName(resolveElaboratedType(type)); +} + +Diagnostic::Diagnostic(const QString &m, const CXCursor &c, CXDiagnosticSeverity s) + : message(m), source(Other), severity(s) +{ + setLocation(getCursorLocation(c)); +} + +Diagnostic Diagnostic::fromCXDiagnostic(CXDiagnostic cd) +{ + Diagnostic result; + result.source = Clang; + CXString spelling = clang_getDiagnosticSpelling(cd); + result.message = QString::fromUtf8(clang_getCString(spelling)); + clang_disposeString(spelling); + result.severity = clang_getDiagnosticSeverity(cd); + result.setLocation(getExpansionLocation(clang_getDiagnosticLocation(cd))); + + CXDiagnosticSet childDiagnostics = clang_getChildDiagnostics(cd); + if (const unsigned childCount = clang_getNumDiagnosticsInSet(childDiagnostics)) { + result.childMessages.reserve(int(childCount)); + const unsigned format = clang_defaultDiagnosticDisplayOptions(); + for (unsigned i = 0; i < childCount; ++i) { + CXDiagnostic childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, i); + CXString cdm = clang_formatDiagnostic(childDiagnostic, format); + result.childMessages.append(QString::fromUtf8(clang_getCString(cdm))); + clang_disposeString(cdm); + clang_disposeDiagnostic(childDiagnostic); + } + } + + return result; +} + +void Diagnostic::setLocation(const SourceLocation &sourceLocation) +{ + file = getFileName(sourceLocation.file); + line = sourceLocation.line; + column = sourceLocation.column; + offset = sourceLocation.offset; +} + +QList<Diagnostic> getDiagnostics(CXTranslationUnit tu) +{ + QList<Diagnostic> result; + const unsigned count = clang_getNumDiagnostics(tu); + result.reserve(int(count)); + for (unsigned i = 0; i < count; ++i) { + const CXDiagnostic d = clang_getDiagnostic(tu, i); + result.append(Diagnostic::fromCXDiagnostic(d)); + clang_disposeDiagnostic(d); + } + return result; +} + +std::pair<qsizetype, qsizetype> + parseTemplateArgumentList(const QString &l, + const TemplateArgumentHandler &handler, + qsizetype from) +{ + const auto ltPos = l.indexOf(u'<', from); + if (ltPos == - 1) + return std::make_pair(-1, -1); + auto startPos = ltPos + 1; + int level = 1; + for (qsizetype p = startPos, end = l.size(); p < end; ) { + const char c = l.at(p).toLatin1(); + switch (c) { + case ',': + case '>': + handler(level, QStringView{l}.mid(startPos, p - startPos).trimmed()); + ++p; + if (c == '>') { + if (--level == 0) + return std::make_pair(ltPos, p); + // Skip over next ',': "a<b<c,d>,e>" + for (; p < end && (l.at(p).isSpace() || l.at(p) == u','); ++p) {} + } + startPos = p; + break; + case '<': + handler(level, QStringView{l}.mid(startPos, p - startPos).trimmed()); + ++level; + startPos = ++p; + break; + default: + ++p; + break; + } + } + return std::make_pair(-1, -1); +} + +CXDiagnosticSeverity maxSeverity(const QList<Diagnostic> &ds) +{ + CXDiagnosticSeverity result = CXDiagnostic_Ignored; + for (const Diagnostic& d : ds) { + if (d.severity > result) + result = d.severity; + } + return result; +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug s, const SourceLocation &l) +{ + QDebugStateSaver saver(s); + s.nospace(); + s.noquote(); + s << QDir::toNativeSeparators(clang::getFileName(l.file)) << ':' << l.line; + if (l.column) + s << ':' << l.column; + return s; +} + +// Roughly follow g++ format: +// file.cpp:214:37: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] +QDebug operator<<(QDebug s, const Diagnostic &d) +{ + QDebugStateSaver saver(s); + s.nospace(); + s.noquote(); + s << d.file << ':'<< d.line << ':' << d.column << ": "; + switch (d.severity) { + case CXDiagnostic_Ignored: + s << "ignored"; + break; + case CXDiagnostic_Note: + s << "note"; + break; + case CXDiagnostic_Warning: + s << "warning"; + break; + case CXDiagnostic_Error: + s << "error"; + break; + case CXDiagnostic_Fatal: + s << "fatal"; + break; + } + s << ": " << d.message; + + if (d.source != Diagnostic::Clang) + s << " [other]"; + + if (const auto childMessagesCount = d.childMessages.size()) { + s << '\n'; + for (qsizetype i = 0; i < childMessagesCount; ++i) + s << " " << d.childMessages.at(i) << '\n'; + } + + return s; +} + +#endif // QT_NO_DEBUG_STREAM + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/clangutils.h b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h new file mode 100644 index 000000000..fbbf95f1b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/clangutils.h @@ -0,0 +1,108 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLANGUTILS_H +#define CLANGUTILS_H + +#include <clang-c/Index.h> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QtCompare> +#include <QtCore/QList> + +#include <functional> +#include <utility> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +bool operator==(const CXCursor &c1, const CXCursor &c2) noexcept; +size_t qHash(const CXCursor &c, size_t seed = 0) noexcept; + +bool operator==(const CXType &t1, const CXType &t2) noexcept; +size_t qHash(const CXType &ct, size_t seed = 0) noexcept; + +namespace clang { + +QString getCursorKindName(CXCursorKind cursorKind); +QString getCursorSpelling(const CXCursor &cursor); +QString getCursorDisplayName(const CXCursor &cursor); +QString getTypeName(const CXType &type); +bool hasScopeResolution(const CXType &type); +CXType fullyResolveType(const CXType &type); +QString getResolvedTypeName(const CXType &type); +inline QString getCursorTypeName(const CXCursor &cursor) + { return getTypeName(clang_getCursorType(cursor)); } +inline QString getCursorResultTypeName(const CXCursor &cursor) + { return getTypeName(clang_getCursorResultType(cursor)); } + +inline bool isCursorValid(const CXCursor &c) +{ + return c.kind < CXCursor_FirstInvalid || c.kind > CXCursor_LastInvalid; +} + +QString getFileName(CXFile file); // Uncached,see BaseVisitor for a cached version + +struct SourceLocation +{ + bool equals(const SourceLocation &rhs) const; + + CXFile file = nullptr; + unsigned line = 0; + unsigned column = 0; + unsigned offset = 0; + + friend constexpr bool comparesEqual(const SourceLocation &lhs, + const SourceLocation &rhs) noexcept + { + return lhs.file == rhs.file && lhs.offset == rhs.offset; + } + Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(SourceLocation) +}; + +SourceLocation getExpansionLocation(const CXSourceLocation &location); + +using SourceRange = std::pair<SourceLocation, SourceLocation>; + +SourceLocation getCursorLocation(const CXCursor &cursor); +CXString getFileNameFromLocation(const CXSourceLocation &location); +SourceRange getCursorRange(const CXCursor &cursor); + +struct Diagnostic { + enum Source { Clang, Other }; + + Diagnostic() = default; + // Clang + static Diagnostic fromCXDiagnostic(CXDiagnostic cd); + // Other + explicit Diagnostic(const QString &m, const CXCursor &c, CXDiagnosticSeverity s = CXDiagnostic_Warning); + void setLocation(const SourceLocation &); + + QString message; + QStringList childMessages; + QString file; + unsigned line = 0; + unsigned column = 0; + unsigned offset = 0; + Source source = Clang; + CXDiagnosticSeverity severity = CXDiagnostic_Warning; +}; + +QList<Diagnostic> getDiagnostics(CXTranslationUnit tu); +CXDiagnosticSeverity maxSeverity(const QList<Diagnostic> &ds); + +// Parse a template argument list "a<b<c,d>,e>" and invoke a handler +// with each match (level and string). Return begin and end of the list. +using TemplateArgumentHandler = std::function<void (int, QStringView)>; + +std::pair<qsizetype, qsizetype> + parseTemplateArgumentList(const QString &l, + const TemplateArgumentHandler &handler, + qsizetype from = 0); + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug, const SourceLocation &); +QDebug operator<<(QDebug, const Diagnostic &); +#endif // QT_NO_DEBUG_STREAM +} // namespace clang + +#endif // CLANGUTILS_H diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp new file mode 100644 index 000000000..20224020b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.cpp @@ -0,0 +1,456 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "compilersupport.h" +#include "header_paths.h" +#include "clangutils.h" + +#include <reporthandler.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QProcess> +#include <QtCore/QStandardPaths> +#include <QtCore/QStringList> +#include <QtCore/QVersionNumber> + +#include <clang-c/Index.h> + +#include <algorithm> +#include <iterator> + +using namespace Qt::StringLiterals; + +namespace clang { + +QVersionNumber libClangVersion() +{ + return QVersionNumber(CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR); +} + +static Compiler _compiler = +#if defined (Q_CC_CLANG) + Compiler::Clang; +#elif defined (Q_CC_MSVC) + Compiler::Msvc; +#else + Compiler::Gpp; +#endif + +Compiler compiler() { return _compiler; } + +bool setCompiler(const QString &name) +{ + bool result = true; + if (name == u"msvc") + _compiler = Compiler::Msvc; + else if (name == u"g++") + _compiler = Compiler::Gpp; + else if (name == u"clang") + _compiler = Compiler::Clang; + else + result = false; + return result; +} + +QString _compilerPath; // Pre-defined compiler path (from command line) + +const QString &compilerPath() +{ + return _compilerPath; +} + +void setCompilerPath(const QString &name) +{ + _compilerPath = name; +} + +static Platform _platform = +#if defined (Q_OS_DARWIN) + Platform::macOS; +#elif defined (Q_OS_WIN) + Platform::Windows; +#else + Platform::Unix; +#endif + +Platform platform() { return _platform; } + +bool setPlatform(const QString &name) +{ + bool result = true; + if (name == u"windows") + _platform = Platform::Windows; + else if (name == u"darwin") + _platform = Platform::macOS; + else if (name == u"unix") + _platform = Platform::Unix; + else + result = false; + return result; +} + +// 3/2024: Use a recent MSVC2022 for libclang 18.X +static QByteArray msvcCompatVersion() +{ + return libClangVersion() >= QVersionNumber(0, 64) ? "19.39"_ba : "19.26"_ba; +} + +static bool runProcess(const QString &program, const QStringList &arguments, + QByteArray *stdOutIn = nullptr, QByteArray *stdErrIn = nullptr) +{ + QProcess process; + process.start(program, arguments, QProcess::ReadWrite); + if (!process.waitForStarted()) { + qCWarning(lcShiboken).noquote().nospace() << "Unable to start " + << process.program() << ": " << process.errorString(); + return false; + } + process.closeWriteChannel(); + const bool finished = process.waitForFinished(); + const QByteArray stdErr = process.readAllStandardError(); + if (stdErrIn) + *stdErrIn = stdErr; + if (stdOutIn) + *stdOutIn = process.readAllStandardOutput(); + + if (!finished) { + qCWarning(lcShiboken).noquote().nospace() << process.program() << " timed out: " << stdErr; + process.kill(); + return false; + } + + if (process.exitStatus() != QProcess::NormalExit) { + qCWarning(lcShiboken).noquote().nospace() << process.program() << " crashed: " << stdErr; + return false; + } + + if (process.exitCode() != 0) { + qCWarning(lcShiboken).noquote().nospace() << process.program() << " exited " + << process.exitCode() << ": " << stdErr; + return false; + } + + return true; +} + +static QByteArray frameworkPath() { return QByteArrayLiteral(" (framework directory)"); } + +static void filterHomebrewHeaderPaths(HeaderPaths &headerPaths) +{ + QByteArray homebrewPrefix = qgetenv("HOMEBREW_OPT"); + + // If HOMEBREW_OPT is found we assume that the build is happening + // inside a brew environment, which means we need to filter out + // the -isystem flags added by the brew clang shim. This is needed + // because brew passes the Qt include paths as system include paths + // and because our parser ignores system headers, Qt classes won't + // be found and thus compilation errors will occur. + if (homebrewPrefix.isEmpty()) + return; + + qCInfo(lcShiboken) << "Found HOMEBREW_OPT with value:" << homebrewPrefix + << "Assuming homebrew build environment."; + + HeaderPaths::iterator it = headerPaths.begin(); + while (it != headerPaths.end()) { + if (it->path.startsWith(homebrewPrefix)) { + qCInfo(lcShiboken) << "Filtering out homebrew include path: " + << it->path; + it = headerPaths.erase(it); + } else { + ++it; + } + } +} + +// Determine g++'s internal include paths from the output of +// g++ -E -x c++ - -v </dev/null +// Output looks like: +// #include <...> search starts here: +// /usr/local/include +// /System/Library/Frameworks (framework directory) +// End of search list. +static HeaderPaths gppInternalIncludePaths(const QString &compiler) +{ + HeaderPaths result; + QStringList arguments{u"-E"_s, u"-x"_s, u"c++"_s, u"-"_s, u"-v"_s}; + QByteArray stdOut; + QByteArray stdErr; + if (!runProcess(compiler, arguments, &stdOut, &stdErr)) + return result; + const QByteArrayList stdErrLines = stdErr.split('\n'); + bool isIncludeDir = false; + + if (ReportHandler::isDebug(ReportHandler::MediumDebug)) + qCInfo(lcShiboken()).noquote().nospace() + << "gppInternalIncludePaths:\n compiler: " << compiler + << "\n stdOut: " << stdOut + << "\n stdErr: " << stdErr; + + for (const QByteArray &line : stdErrLines) { + if (isIncludeDir) { + if (line.startsWith(QByteArrayLiteral("End of search list"))) { + isIncludeDir = false; + } else { + HeaderPath headerPath{line.trimmed(), HeaderType::System}; + if (headerPath.path.endsWith(frameworkPath())) { + headerPath.type = HeaderType::FrameworkSystem; + headerPath.path.truncate(headerPath.path.size() - frameworkPath().size()); + } + result.append(headerPath); + } + } else if (line.startsWith(QByteArrayLiteral("#include <...> search starts here"))) { + isIncludeDir = true; + } + } + + if (platform() == Platform::macOS) + filterHomebrewHeaderPaths(result); + + return result; +} + +// Detect Vulkan as supported from Qt 5.10 by checking the environment variables. +QByteArrayList detectVulkan() +{ + static const char *vulkanVariables[] = {"VULKAN_SDK", "VK_SDK_PATH"}; + for (const char *vulkanVariable : vulkanVariables) { + if (qEnvironmentVariableIsSet(vulkanVariable)) { + const auto option = QByteArrayLiteral("-isystem") + + qgetenv(vulkanVariable) + + QByteArrayLiteral("/include"); + return {option}; + } + } + return {}; +} + +// For MSVC, we set the MS compatibility version and let Clang figure out its own +// options and include paths. +// For the others, we pass "-nostdinc" since libclang tries to add it's own system +// include paths, which together with the clang compiler paths causes some clash +// which causes std types not being found and construct -I/-F options from the +// include paths of the host compiler. + +static QByteArray noStandardIncludeOption() { return QByteArrayLiteral("-nostdinc"); } + +// The clang builtin includes directory is used to find the definitions for +// intrinsic functions and builtin types. It is necessary to use the clang +// includes to prevent redefinition errors. The default toolchain includes +// should be picked up automatically by clang without specifying +// them implicitly. + +// Besides g++/Linux, as of MSVC 19.28.29334, MSVC needs clang includes +// due to PYSIDE-1433, LLVM-47099 + +static bool needsClangBuiltinIncludes() +{ + return platform() != Platform::macOS; +} + +static QString queryLlvmConfigDir(const QString &arg) +{ + static const QString llvmConfig = QStandardPaths::findExecutable(u"llvm-config"_s); + if (llvmConfig.isEmpty()) + return {}; + QByteArray stdOut; + if (!runProcess(llvmConfig, QStringList{arg}, &stdOut)) + return {}; + const QString path = QFile::decodeName(stdOut.trimmed()); + if (!QFileInfo::exists(path)) { + qCWarning(lcShiboken, R"(%s: "%s" as returned by llvm-config "%s" does not exist.)", + __FUNCTION__, qPrintable(QDir::toNativeSeparators(path)), qPrintable(arg)); + return {}; + } + return path; +} + +static QString findClangLibDir() +{ + for (const char *envVar : {"LLVM_INSTALL_DIR", "CLANG_INSTALL_DIR"}) { + if (qEnvironmentVariableIsSet(envVar)) { + const QString path = QFile::decodeName(qgetenv(envVar)) + u"/lib"_s; + if (QFileInfo::exists(path)) + return path; + qCWarning(lcShiboken, "%s: %s as pointed to by %s does not exist.", + __FUNCTION__, qPrintable(path), envVar); + } + } + return queryLlvmConfigDir(u"--libdir"_s); +} + +static QString findClangBuiltInIncludesDir() +{ + // Find the include directory of the highest version. + const QString clangPathLibDir = findClangLibDir(); + if (!clangPathLibDir.isEmpty()) { + QString candidate; + QString clangDirName = clangPathLibDir + u"/clang"_s; + // PYSIDE-2769: llvm-config --libdir may report /usr/lib64 on manylinux_2_28_x86_64 + // whereas the includes are under /usr/lib/clang/../include. + if (!QFileInfo::exists(clangDirName) && clangPathLibDir.endsWith("64"_L1)) { + const QString fallback = clangPathLibDir.sliced(0, clangPathLibDir.size() - 2); + clangDirName = fallback + u"/clang"_s; + qCWarning(lcShiboken, "%s: Falling back from %s to %s.", + __FUNCTION__, qPrintable(clangPathLibDir), qPrintable(fallback)); + } + + QVersionNumber lastVersionNumber(1, 0, 0); + QDir clangDir(clangDirName); + const QFileInfoList versionDirs = + clangDir.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + if (versionDirs.isEmpty()) + qCWarning(lcShiboken, "%s: No subdirectories found in %s.", + __FUNCTION__, qPrintable(clangDirName)); + for (const QFileInfo &fi : versionDirs) { + const QString fileName = fi.fileName(); + if (fileName.at(0).isDigit()) { + const QVersionNumber versionNumber = QVersionNumber::fromString(fileName); + if (!versionNumber.isNull() && versionNumber > lastVersionNumber) { + candidate = fi.absoluteFilePath(); + lastVersionNumber = versionNumber; + } + } + } + if (!candidate.isEmpty()) + return candidate + "/include"_L1; + } + return queryLlvmConfigDir(u"--includedir"_s); +} + +QString compilerFromCMake() +{ +#ifdef CMAKE_CXX_COMPILER + return QString::fromLocal8Bit(CMAKE_CXX_COMPILER); +#else + return {}; +#endif +} + +// Return a compiler suitable for determining the internal include paths +static QString compilerFromCMake(const QString &defaultCompiler) +{ + if (!compilerPath().isEmpty()) + return compilerPath(); + // Exclude macOS since cmakeCompiler returns the full path instead of the + // /usr/bin/clang shim, which results in the default SDK sysroot path + // missing (PYSIDE-1032) + if (platform() == Platform::macOS) + return defaultCompiler; + QString cmakeCompiler = compilerFromCMake(); + if (cmakeCompiler.isEmpty()) + return defaultCompiler; + QFileInfo fi(cmakeCompiler); + // Should be absolute by default, but a user may specify -DCMAKE_CXX_COMPILER=cl.exe + if (fi.isRelative()) + return cmakeCompiler; + if (fi.exists()) + return fi.absoluteFilePath(); + // The compiler may not exist in case something like icecream or + // a non-standard-path was used on the build machine. Check + // the executable. + cmakeCompiler = QStandardPaths::findExecutable(fi.fileName()); + return cmakeCompiler.isEmpty() ? defaultCompiler : cmakeCompiler; +} + +static void appendClangBuiltinIncludes(HeaderPaths *p) +{ + const QString clangBuiltinIncludesDir = + QDir::toNativeSeparators(findClangBuiltInIncludesDir()); + if (clangBuiltinIncludesDir.isEmpty()) { + qCWarning(lcShiboken, "Unable to locate Clang's built-in include directory " + "(neither by checking the environment variables LLVM_INSTALL_DIR, CLANG_INSTALL_DIR " + " nor running llvm-config). This may lead to parse errors."); + } else { + qCInfo(lcShiboken, "CLANG v%d.%d, builtins includes directory: %s", + CINDEX_VERSION_MAJOR, CINDEX_VERSION_MINOR, + qPrintable(clangBuiltinIncludesDir)); + p->append(HeaderPath{QFile::encodeName(clangBuiltinIncludesDir), + HeaderType::System}); + } +} + +// Returns clang options needed for emulating the host compiler +QByteArrayList emulatedCompilerOptions(LanguageLevel level) +{ + QByteArrayList result; + HeaderPaths headerPaths; + switch (compiler()) { + case Compiler::Msvc: + result.append("-fms-compatibility-version="_ba + msvcCompatVersion()); + if (level < LanguageLevel::Cpp20) + result.append("-fdelayed-template-parsing"_ba); + result.append(QByteArrayLiteral("-Wno-microsoft-enum-value")); + result.append("/Zc:__cplusplus"_ba); + // Fix yvals_core.h: STL1000: Unexpected compiler version, expected Clang 7 or newer (MSVC2017 update) + result.append(QByteArrayLiteral("-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH")); + if (needsClangBuiltinIncludes()) + appendClangBuiltinIncludes(&headerPaths); + break; + case Compiler::Clang: + headerPaths.append(gppInternalIncludePaths(compilerFromCMake(u"clang++"_s))); + result.append(noStandardIncludeOption()); + break; + case Compiler::Gpp: + if (needsClangBuiltinIncludes()) + appendClangBuiltinIncludes(&headerPaths); + + // Append the c++ include paths since Clang is unable to find + // <type_traits> etc (g++ 11.3). + const HeaderPaths gppPaths = gppInternalIncludePaths(compilerFromCMake(u"g++"_s)); + for (const HeaderPath &h : gppPaths) { + if (h.path.contains("c++") || h.path.contains("sysroot")) + headerPaths.append(h); + } + break; + } + + std::transform(headerPaths.cbegin(), headerPaths.cend(), + std::back_inserter(result), HeaderPath::includeOption); + return result; +} + +LanguageLevel emulatedCompilerLanguageLevel() +{ + return LanguageLevel::Cpp17; +} + +struct LanguageLevelMapping +{ + const char *option; + LanguageLevel level; +}; + +static const LanguageLevelMapping languageLevelMapping[] = +{ + {"c++11", LanguageLevel::Cpp11}, + {"c++14", LanguageLevel::Cpp14}, + {"c++17", LanguageLevel::Cpp17}, + {"c++20", LanguageLevel::Cpp20}, + {"c++1z", LanguageLevel::Cpp1Z} +}; + +const char *languageLevelOption(LanguageLevel l) +{ + for (const LanguageLevelMapping &m : languageLevelMapping) { + if (m.level == l) + return m.option; + } + return nullptr; +} + +LanguageLevel languageLevelFromOption(const char *o) +{ + for (const LanguageLevelMapping &m : languageLevelMapping) { + if (!strcmp(m.option, o)) + return m.level; + } + return LanguageLevel::Default; +} + +} // namespace clang diff --git a/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h new file mode 100644 index 000000000..f1d63b7c3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/clangparser/compilersupport.h @@ -0,0 +1,56 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef COMPILERSUPPORT_H +#define COMPILERSUPPORT_H + +#include <QtCore/QByteArrayList> + +QT_FORWARD_DECLARE_CLASS(QVersionNumber) +QT_FORWARD_DECLARE_CLASS(QString) + +enum class LanguageLevel { + Default, + Cpp11, + Cpp14, + Cpp17, + Cpp20, + Cpp1Z +}; + +enum class Compiler { + Msvc, + Gpp, + Clang +}; + +enum class Platform { + Unix, + Windows, + macOS +}; + +namespace clang { +QVersionNumber libClangVersion(); + +QByteArrayList emulatedCompilerOptions(LanguageLevel level); +LanguageLevel emulatedCompilerLanguageLevel(); + +const char *languageLevelOption(LanguageLevel l); +LanguageLevel languageLevelFromOption(const char *); + +QByteArrayList detectVulkan(); + +Compiler compiler(); +bool setCompiler(const QString &name); + +QString compilerFromCMake(); + +const QString &compilerPath(); +void setCompilerPath(const QString &name); + +Platform platform(); +bool setPlatform(const QString &name); +} // namespace clang + +#endif // COMPILERSUPPORT_H diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.cpp b/sources/shiboken6/ApiExtractor/classdocumentation.cpp new file mode 100644 index 000000000..637e4a422 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/classdocumentation.cpp @@ -0,0 +1,381 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "classdocumentation.h" +#include "messages.h" +#include "debughelpers_p.h" + +#include <QtCore/QDebug> +#include <QtCore/QBuffer> +#include <QtCore/QFile> +#include <QtCore/QXmlStreamReader> +#include <QtCore/QXmlStreamAttributes> +#include <QtCore/QXmlStreamWriter> + +#include <algorithm> + +using namespace Qt::StringLiterals; + +// Sort functions by name and argument count +static bool functionDocumentationLessThan(const FunctionDocumentation &f1, + const FunctionDocumentation &f2) +{ + const int nc = f1.name.compare(f2.name); + if (nc != 0) + return nc < 0; + return f1.parameters.size() < f2.parameters.size(); +} + +static void sortDocumentation(ClassDocumentation *cd) +{ + std::stable_sort(cd->enums.begin(), cd->enums.end(), + [] (const EnumDocumentation &e1, const EnumDocumentation &e2) { + return e1.name < e2.name; }); + std::stable_sort(cd->properties.begin(), cd->properties.end(), + [] (const PropertyDocumentation &p1, const PropertyDocumentation &p2) { + return p1.name < p2.name; }); + std::stable_sort(cd->functions.begin(), cd->functions.end(), + functionDocumentationLessThan); +} + +qsizetype ClassDocumentation::indexOfEnum(const QString &name) const +{ + for (qsizetype i = 0, size = enums.size(); i < size; ++i) { + if (enums.at(i).name == name) + return i; + } + return -1; +} + +FunctionDocumentationList ClassDocumentation::findFunctionCandidates(const QString &name, + bool constant) const +{ + FunctionDocumentationList result; + std::copy_if(functions.cbegin(), functions.cend(), + std::back_inserter(result), + [name, constant](const FunctionDocumentation &fd) { + return fd.constant == constant && fd.name == name; + }); + return result; +} + +static bool matches(const FunctionDocumentation &fd, const FunctionDocumentationQuery &q) +{ + return fd.name == q.name && fd.constant == q.constant && fd.parameters == q.parameters; +} + +qsizetype ClassDocumentation::indexOfFunction(const FunctionDocumentationList &fl, + const FunctionDocumentationQuery &q) +{ + for (qsizetype i = 0, size = fl.size(); i < size; ++i) { + if (matches(fl.at(i), q)) + return i; + } + return -1; +} + +qsizetype ClassDocumentation::indexOfProperty(const QString &name) const +{ + for (qsizetype i = 0, size = properties.size(); i < size; ++i) { + if (properties.at(i).name == name) + return i; + } + return -1; +} + +enum class WebXmlCodeTag +{ + Class, Description, Enum, Function, Header, Parameter, Property, Typedef, Other +}; + +static WebXmlCodeTag tag(QStringView name) +{ + if (name == u"class" || name == u"namespace") + return WebXmlCodeTag::Class; + if (name == u"enum") + return WebXmlCodeTag::Enum; + if (name == u"function") + return WebXmlCodeTag::Function; + if (name == u"description") + return WebXmlCodeTag::Description; + if (name == u"header") + return WebXmlCodeTag::Header; + if (name == u"parameter") + return WebXmlCodeTag::Parameter; + if (name == u"property") + return WebXmlCodeTag::Property; + if (name == u"typedef") + return WebXmlCodeTag::Typedef; + return WebXmlCodeTag::Other; +} + +static void parseWebXmlElement(WebXmlCodeTag tag, const QXmlStreamAttributes &attributes, + ClassDocumentation *cd) +{ + switch (tag) { + case WebXmlCodeTag::Class: + cd->name = attributes.value(u"name"_s).toString(); + cd->type = ClassDocumentation::Class; + break; + case WebXmlCodeTag::Header: + cd->name = attributes.value(u"name"_s).toString(); + cd->type = ClassDocumentation::Header; + break; + case WebXmlCodeTag::Enum: { + EnumDocumentation ed; + ed.name = attributes.value(u"name"_s).toString(); + cd->enums.append(ed); + } + break; + case WebXmlCodeTag::Function: { + FunctionDocumentation fd; + fd.name = attributes.value(u"name"_s).toString(); + fd.signature = attributes.value(u"signature"_s).toString(); + fd.returnType = attributes.value(u"type"_s).toString(); + fd.constant = attributes.value(u"const"_s) == u"true"; + cd->functions.append(fd); + } + break; + case WebXmlCodeTag::Parameter: + Q_ASSERT(!cd->functions.isEmpty()); + cd->functions.last().parameters.append(attributes.value(u"type"_s).toString()); + break; + case WebXmlCodeTag::Property: { + PropertyDocumentation pd; + pd.name = attributes.value(u"name"_s).toString(); + pd.brief = attributes.value(u"brief"_s).toString(); + cd->properties.append(pd); + } + break; + default: + break; + } +} + +// Retrieve the contents of <description> +static QString extractWebXmlDescription(QXmlStreamReader &reader) +{ + QBuffer buffer; + buffer.open(QIODeviceBase::WriteOnly); + QXmlStreamWriter writer(&buffer); + + do { + switch (reader.tokenType()) { + case QXmlStreamReader::StartElement: + writer.writeStartElement(reader.name().toString()); + writer.writeAttributes(reader.attributes()); + break; + case QXmlStreamReader::Characters: + writer.writeCharacters(reader.text().toString()); + break; + case QXmlStreamReader::EndElement: + writer.writeEndElement(); + if (reader.name() == u"description") { + buffer.close(); + return QString::fromUtf8(buffer.buffer()).trimmed(); + } + break; + default: + break; + } + reader.readNext(); + } while (!reader.atEnd()); + + return {}; +} + +static QString msgXmlError(const QString &fileName, const QXmlStreamReader &reader) +{ + QString result; + QTextStream(&result) << fileName << ':' << reader.lineNumber() << ':' + << reader.columnNumber() << ':' << reader.errorString(); + return result; +} + +std::optional<ClassDocumentation> parseWebXml(const QString &fileName, QString *errorMessage) +{ + ClassDocumentation result; + + QFile file(fileName); + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { + *errorMessage = msgCannotOpenForReading(file); + return std::nullopt; + } + + WebXmlCodeTag lastTag = WebXmlCodeTag::Other; + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: { + const auto currentTag = tag(reader.name()); + parseWebXmlElement(currentTag, reader.attributes(), &result); + switch (currentTag) { // Store relevant tags in lastTag + case WebXmlCodeTag::Class: + case WebXmlCodeTag::Function: + case WebXmlCodeTag::Enum: + case WebXmlCodeTag::Header: + case WebXmlCodeTag::Property: + case WebXmlCodeTag::Typedef: + lastTag = currentTag; + break; + case WebXmlCodeTag::Description: { // Append the description to the element + QString *target = nullptr; + switch (lastTag) { + case WebXmlCodeTag::Class: + target = &result.description; + break; + case WebXmlCodeTag::Function: + target = &result.functions.last().description; + break; + case WebXmlCodeTag::Enum: + target = &result.enums.last().description; + break; + case WebXmlCodeTag::Property: + target = &result.properties.last().description; + default: + break; + } + if (target != nullptr && target->isEmpty()) + *target = extractWebXmlDescription(reader); + } + break; + default: + break; + } + } + default: + break; + } + } + + if (reader.error() != QXmlStreamReader::NoError) { + *errorMessage= msgXmlError(fileName, reader); + return std::nullopt; + } + + sortDocumentation(&result); + return result; +} + +QString webXmlModuleDescription(const QString &fileName, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::Text | QIODevice::ReadOnly)) { + *errorMessage = msgCannotOpenForReading(file); + return {}; + } + + QString result; + QXmlStreamReader reader(&file); + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: + if (reader.name() == u"description") + result = extractWebXmlDescription(reader); + break; + default: + break; + } + } + + if (reader.error() != QXmlStreamReader::NoError) { + *errorMessage= msgXmlError(fileName, reader); + return {}; + } + + return result; +} + +static void formatDescription(QDebug &debug, const QString &desc) +{ + debug << "description="; + if (desc.isEmpty()) { + debug << "<empty>"; + return; + } + if (debug.verbosity() < 3) + debug << desc.size() << " chars"; + else + debug << '"' << desc << '"'; +} + +QDebug operator<<(QDebug debug, const EnumDocumentation &e) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Enum("; + if (e.name.isEmpty()) { + debug << "invalid"; + } else { + debug << e.name << ", "; + formatDescription(debug, e.description); + } + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const PropertyDocumentation &p) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Property("; + if (p.name.isEmpty()) { + debug << "invalid"; + } else { + debug << p.name << ", "; + formatDescription(debug, p.description); + } + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const FunctionDocumentation &f) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Function("; + if (f.name.isEmpty()) { + debug << "invalid"; + } else { + debug << f.name; + if (!f.returnType.isEmpty()) + debug << ", returns " << f.returnType; + if (f.constant) + debug << ", const"; + formatList(debug, ", parameters", f.parameters, ", "); + debug << ", signature=\"" << f.signature << "\", "; + formatDescription(debug, f.description); + } + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const FunctionDocumentationQuery &q) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "FunctionQuery(" << q.name; + if (q.constant) + debug << ", const"; + formatList(debug, ", parameters", q.parameters); + debug << ')'; + return debug; +} + +QDebug operator<<(QDebug debug, const ClassDocumentation &c) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Class(" << c.name << ", "; + formatDescription(debug, c.description); + formatList(debug, ", enums", c.enums); + formatList(debug, ", properties", c.properties); + formatList(debug, ", functions", c.functions); + debug << ')'; + return debug; +} diff --git a/sources/shiboken6/ApiExtractor/classdocumentation.h b/sources/shiboken6/ApiExtractor/classdocumentation.h new file mode 100644 index 000000000..d47101389 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/classdocumentation.h @@ -0,0 +1,82 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CLASSDOCUMENTATION_H +#define CLASSDOCUMENTATION_H + +#include <QtCore/QStringList> + +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +/// An enumeration in a WebXML/doxygen document +struct EnumDocumentation +{ + QString name; + QString description; +}; + +/// A QObject property in a WebXML/doxygen document +struct PropertyDocumentation +{ + QString name; + QString brief; + QString description; +}; + +/// Helper struct for querying a function in a WebXML/doxygen document +struct FunctionDocumentationQuery +{ + QString name; + QStringList parameters; + bool constant = false; +}; + +/// A function in a WebXML/doxygen document +struct FunctionDocumentation : public FunctionDocumentationQuery +{ + QString signature; + QString returnType; + QString description; +}; + +using FunctionDocumentationList = QList<FunctionDocumentation>; + +/// A WebXML/doxygen document +struct ClassDocumentation +{ + enum Type { + Class, // <class>, class/namespace + Header // <header>, grouped global functions/enums + }; + + qsizetype indexOfEnum(const QString &name) const; + FunctionDocumentationList findFunctionCandidates(const QString &name, + bool constant) const; + static qsizetype indexOfFunction(const FunctionDocumentationList &fl, + const FunctionDocumentationQuery &q); + qsizetype indexOfProperty(const QString &name) const; + + Type type = Type::Class; + QString name; + QString description; + + QList<EnumDocumentation> enums; + QList<PropertyDocumentation> properties; + FunctionDocumentationList functions; +}; + +/// Parse a WebXML class/namespace document +std::optional<ClassDocumentation> parseWebXml(const QString &fileName, QString *errorMessage); + +/// Extract the module description from a WebXML module document +QString webXmlModuleDescription(const QString &fileName, QString *errorMessage); + +QDebug operator<<(QDebug debug, const EnumDocumentation &e); +QDebug operator<<(QDebug debug, const PropertyDocumentation &p); +QDebug operator<<(QDebug debug, const FunctionDocumentationQuery &q); +QDebug operator<<(QDebug debug, const FunctionDocumentation &f); +QDebug operator<<(QDebug debug, const ClassDocumentation &c); + +#endif // CLASSDOCUMENTATION_H diff --git a/sources/shiboken6/ApiExtractor/cmake_uninstall.cmake b/sources/shiboken6/ApiExtractor/cmake_uninstall.cmake new file mode 100644 index 000000000..4031b4e1a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/cmake_uninstall.cmake @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +IF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + MESSAGE(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +ENDIF(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +FILE(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +STRING(REGEX REPLACE "\n" ";" files "${files}") +FOREACH(file ${files}) + MESSAGE(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + IF(EXISTS "$ENV{DESTDIR}${file}") + EXEC_PROGRAM( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + IF(NOT "${rm_retval}" STREQUAL 0) + MESSAGE(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + ENDIF(NOT "${rm_retval}" STREQUAL 0) + ELSE(EXISTS "$ENV{DESTDIR}${file}") + MESSAGE(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + ENDIF(EXISTS "$ENV{DESTDIR}${file}") +ENDFOREACH(file) diff --git a/sources/shiboken6/ApiExtractor/codesnip.cpp b/sources/shiboken6/ApiExtractor/codesnip.cpp new file mode 100644 index 000000000..e2cd5eb35 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/codesnip.cpp @@ -0,0 +1,78 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "codesnip.h" + +#include "qtcompat.h" +#include "exception.h" +#include "typedatabase.h" + +#include <QtCore/QDebug> + +using namespace Qt::StringLiterals; + +QString TemplateInstance::expandCode() const +{ + const auto templateEntry = TypeDatabase::instance()->findTemplate(m_name); + if (!templateEntry) { + const QString m = u"<insert-template> referring to non-existing template '"_s + + m_name + u"'."_s; + throw Exception(m); + } + + QString code = templateEntry->code(); + for (auto it = replaceRules.cbegin(), end = replaceRules.cend(); it != end; ++it) + code.replace(it.key(), it.value()); + while (!code.isEmpty() && code.at(code.size() - 1).isSpace()) + code.chop(1); + QString result = u"// TEMPLATE - "_s + m_name + u" - START"_s; + if (!code.startsWith(u'\n')) + result += u'\n'; + result += code; + result += u"\n// TEMPLATE - "_s + m_name + u" - END\n"_s; + return result; +} + +// ---------------------- CodeSnipFragment +QString CodeSnipFragment::code() const +{ + return m_instance ? m_instance->expandCode() : m_code; +} + +// ---------------------- CodeSnipAbstract +QString CodeSnipAbstract::code() const +{ + QString res; + for (const CodeSnipFragment &codeFrag : codeList) + res.append(codeFrag.code()); + + return res; +} + +void CodeSnipAbstract::addCode(const QString &code) +{ + codeList.append(CodeSnipFragment(fixSpaces(code))); +} + +void CodeSnipAbstract::purgeEmptyFragments() +{ + auto end = std::remove_if(codeList.begin(), codeList.end(), + [](const CodeSnipFragment &f) { return f.isEmpty(); }); + codeList.erase(end, codeList.end()); +} + +QRegularExpression CodeSnipAbstract::placeHolderRegex(int index) +{ + return QRegularExpression(u'%' + QString::number(index) + "\\b"_L1); +} + +void purgeEmptyCodeSnips(QList<CodeSnip> *list) +{ + for (auto it = list->begin(); it != list->end(); ) { + it->purgeEmptyFragments(); + if (it->isEmpty()) + it = list->erase(it); + else + ++it; + } +} diff --git a/sources/shiboken6/ApiExtractor/codesnip.h b/sources/shiboken6/ApiExtractor/codesnip.h new file mode 100644 index 000000000..86834a1db --- /dev/null +++ b/sources/shiboken6/ApiExtractor/codesnip.h @@ -0,0 +1,107 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CODESNIP_H +#define CODESNIP_H + +#include "codesniphelpers.h" +#include "typesystem_enums.h" + +#include <QtCore/QList> +#include <QtCore/QHash> +#include <QtCore/QString> + +#include <memory> + +class TemplateInstance +{ +public: + explicit TemplateInstance(const QString &name) : m_name(name) {} + + void addReplaceRule(const QString &name, const QString &value) + { + replaceRules[name] = value; + } + + QString expandCode() const; + + QString name() const + { + return m_name; + } + +private: + const QString m_name; + QHash<QString, QString> replaceRules; +}; + +using TemplateInstancePtr = std::shared_ptr<TemplateInstance>; + +class CodeSnipFragment +{ +public: + CodeSnipFragment() = default; + explicit CodeSnipFragment(const QString &code) : m_code(code) {} + explicit CodeSnipFragment(const TemplateInstancePtr &instance) : m_instance(instance) {} + + bool isEmpty() const { return m_code.isEmpty() && !m_instance; } + + QString code() const; + + TemplateInstancePtr instance() const { return m_instance; } + +private: + QString m_code; + std::shared_ptr<TemplateInstance> m_instance; +}; + +class CodeSnipAbstract : public CodeSnipHelpers +{ +public: + QString code() const; + + void addCode(const QString &code); + void addCode(QStringView code) { addCode(code.toString()); } + + void addTemplateInstance(const TemplateInstancePtr &ti) + { + codeList.append(CodeSnipFragment(ti)); + } + + bool isEmpty() const { return codeList.isEmpty(); } + void purgeEmptyFragments(); + + QList<CodeSnipFragment> codeList; + + static QRegularExpression placeHolderRegex(int index); +}; + +class TemplateEntry : public CodeSnipAbstract +{ +public: + explicit TemplateEntry(const QString &name) : m_name(name) {} + + QString name() const + { + return m_name; + } + +private: + QString m_name; +}; + +class CodeSnip : public CodeSnipAbstract +{ +public: + CodeSnip() = default; + explicit CodeSnip(TypeSystem::Language lang) : language(lang) {} + + TypeSystem::Language language = TypeSystem::TargetLangCode; + TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionAny; +}; + +/// Purge empty fragments and snippets caused by new line characters in +/// conjunction with <insert-template>. +void purgeEmptyCodeSnips(QList<CodeSnip> *list); + +#endif // CODESNIP_H diff --git a/sources/shiboken6/ApiExtractor/codesniphelpers.cpp b/sources/shiboken6/ApiExtractor/codesniphelpers.cpp new file mode 100644 index 000000000..775cf10af --- /dev/null +++ b/sources/shiboken6/ApiExtractor/codesniphelpers.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "codesniphelpers.h" + +#include <QtCore/QStringList> + +#include <algorithm> + +static inline int firstNonBlank(QStringView s) +{ + const auto it = std::find_if(s.cbegin(), s.cend(), + [] (QChar c) { return !c.isSpace(); }); + return int(it - s.cbegin()); +} + +static inline bool isEmpty(QStringView s) +{ + return s.isEmpty() + || std::all_of(s.cbegin(), s.cend(), + [] (QChar c) { return c.isSpace(); }); +} + +QString CodeSnipHelpers::dedent(const QString &code) +{ + if (code.isEmpty()) + return code; + // Right trim if indent=0, or trim if single line + if (!code.at(0).isSpace() || !code.contains(u'\n')) + return code.trimmed(); + const auto lines = QStringView{code}.split(u'\n'); + int spacesToRemove = std::numeric_limits<int>::max(); + for (const auto &line : lines) { + if (!isEmpty(line)) { + const int nonSpacePos = firstNonBlank(line); + if (nonSpacePos < spacesToRemove) + spacesToRemove = nonSpacePos; + if (spacesToRemove == 0) + return code; + } + } + QString result; + for (const auto &line : lines) { + if (!isEmpty(line) && spacesToRemove < line.size()) + result += line.mid(spacesToRemove).toString(); + result += u'\n'; + } + return result; +} + +QString CodeSnipHelpers::fixSpaces(QString code) +{ + code.remove(u'\r'); + // Check for XML <tag>\n<space>bla... + if (code.startsWith(u"\n ")) + code.remove(0, 1); + while (!code.isEmpty() && code.back().isSpace()) + code.chop(1); + code = dedent(code); + if (!code.isEmpty() && !code.endsWith(u'\n')) + code.append(u'\n'); + return code; +} + +// Prepend a line to the code, observing indentation +void CodeSnipHelpers::prependCode(QString *code, QString firstLine) +{ + while (!code->isEmpty() && code->front() == u'\n') + code->remove(0, 1); + if (!code->isEmpty() && code->front().isSpace()) { + const int indent = firstNonBlank(*code); + firstLine.prepend(QString(indent, u' ')); + } + if (!firstLine.endsWith(u'\n')) + firstLine += u'\n'; + code->prepend(firstLine); +} diff --git a/sources/shiboken6/ApiExtractor/codesniphelpers.h b/sources/shiboken6/ApiExtractor/codesniphelpers.h new file mode 100644 index 000000000..e7a7545da --- /dev/null +++ b/sources/shiboken6/ApiExtractor/codesniphelpers.h @@ -0,0 +1,17 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CODESNIPHELPERS_H +#define CODESNIPHELPERS_H + +#include <QtCore/QString> + +class CodeSnipHelpers +{ +public: + static QString fixSpaces(QString code); + static QString dedent(const QString &code); + static void prependCode(QString *code, QString firstLine); +}; + +#endif // CODESNIPHELPERS_H diff --git a/sources/shiboken6/ApiExtractor/complextypeentry.h b/sources/shiboken6/ApiExtractor/complextypeentry.h new file mode 100644 index 000000000..5b884f2cc --- /dev/null +++ b/sources/shiboken6/ApiExtractor/complextypeentry.h @@ -0,0 +1,179 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef COMPLEXTYPEENTRY_H +#define COMPLEXTYPEENTRY_H + +#include "configurabletypeentry.h" +#include "typesystem_enums.h" +#include "modifications_typedefs.h" +#include "pymethoddefentry.h" + +#include <QtCore/QSet> + +class ComplexTypeEntryPrivate; + +struct TypeSystemPyMethodDefEntry : public PyMethodDefEntry +{ + QStringList signatures; +}; + +struct TypeSystemProperty +{ + bool isValid() const { return !name.isEmpty() && !read.isEmpty() && !type.isEmpty(); } + + QString type; + QString name; + QString read; + QString write; + QString reset; + QString designable; + QString notify; // Q_PROPERTY/C++ only + // Indicates whether actual code is generated instead of relying on libpyside. + bool generateGetSetDef = false; +}; + +class ComplexTypeEntry : public ConfigurableTypeEntry +{ +public: + enum TypeFlag { + DisableWrapper = 0x1, + Deprecated = 0x4, + ForceAbstract = 0x8, + // Indicates that the instances are used to create hierarchies + // like widgets; parent ownership heuristics are enabled for them. + ParentManagement = 0x10, + DisableQtMetaObjectFunctions = 0x20, + Typedef = 0x40 // Result of a <typedef-type> + }; + Q_DECLARE_FLAGS(TypeFlags, TypeFlag) + + enum CopyableFlag { + CopyableSet, + NonCopyableSet, + Unknown + }; + + explicit ComplexTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + bool isComplex() const override; + + TypeFlags typeFlags() const; + void setTypeFlags(TypeFlags flags); + + // Override command line options to generate nb_bool from + // operator bool or method isNull(). + TypeSystem::BoolCast operatorBoolMode() const; + void setOperatorBoolMode(TypeSystem::BoolCast b); + TypeSystem::BoolCast isNullMode() const; + void setIsNullMode(TypeSystem::BoolCast b); + + FunctionModificationList functionModifications() const; + void setFunctionModifications(const FunctionModificationList &functionModifications); + void addFunctionModification(const FunctionModification &functionModification); + FunctionModificationList functionModifications(const QStringList &signatures) const; + + const CodeSnipList &codeSnips() const; + CodeSnipList &codeSnips(); + void setCodeSnips(const CodeSnipList &codeSnips); + void addCodeSnip(const CodeSnip &codeSnip); + + void setDocModification(const DocModificationList& docMods); + /// Class documentation modifications + DocModificationList docModifications() const; + /// Function documentation modifications (matching signature) + DocModificationList functionDocModifications() const; + + /// Extra includes for function arguments determined by the meta builder. + const IncludeList &argumentIncludes() const; + void addArgumentInclude(const Include &newInclude); + + AddedFunctionList addedFunctions() const; + void setAddedFunctions(const AddedFunctionList &addedFunctions); + void addNewFunction(const AddedFunctionPtr &addedFunction); + + const QList<TypeSystemPyMethodDefEntry> &addedPyMethodDefEntrys() const; + void addPyMethodDef(const TypeSystemPyMethodDefEntry &p); + + // Functions specified in the "generate-functions" attribute + const QSet<QString> &generateFunctions() const; + void setGenerateFunctions(const QSet<QString> &f); + + void setFieldModifications(const FieldModificationList &mods); + FieldModificationList fieldModifications() const; + + const QList<TypeSystemProperty> &properties() const; + void addProperty(const TypeSystemProperty &p); + + QString defaultSuperclass() const; + void setDefaultSuperclass(const QString &sc); + + QString qualifiedCppName() const override; + + void setIsPolymorphicBase(bool on); + bool isPolymorphicBase() const; + + void setPolymorphicIdValue(const QString &value); + QString polymorphicIdValue() const; + + QString polymorphicNameFunction() const; + void setPolymorphicNameFunction(const QString &n); + + QString targetType() const; + void setTargetType(const QString &code); + + bool isGenericClass() const; + void setGenericClass(bool isGeneric); + + bool deleteInMainThread() const; + void setDeleteInMainThread(bool d); + + CopyableFlag copyable() const; + void setCopyable(CopyableFlag flag); + + TypeSystem::QtMetaTypeRegistration qtMetaTypeRegistration() const; + void setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r); + + QString hashFunction() const; + void setHashFunction(const QString &hashFunction); + + void setBaseContainerType(const ComplexTypeEntryCPtr &baseContainer); + + ComplexTypeEntryCPtr baseContainerType() const; + + TypeSystem::ExceptionHandling exceptionHandling() const; + void setExceptionHandling(TypeSystem::ExceptionHandling e); + + TypeSystem::AllowThread allowThread() const; + void setAllowThread(TypeSystem::AllowThread allowThread); + + QString defaultConstructor() const; + void setDefaultConstructor(const QString& defaultConstructor); + bool hasDefaultConstructor() const; + + TypeEntry *clone() const override; + + void useAsTypedef(const ComplexTypeEntryCPtr &source); + + TypeSystem::SnakeCase snakeCase() const; + void setSnakeCase(TypeSystem::SnakeCase sc); + + // Determined by AbstractMetaBuilder from the code model. + bool isValueTypeWithCopyConstructorOnly() const; + void setValueTypeWithCopyConstructorOnly(bool v); + + // FIXME PYSIDE 7: Remove this + static bool isParentManagementEnabled(); + static void setParentManagementEnabled(bool e); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &debug) const override; +#endif +protected: + explicit ComplexTypeEntry(ComplexTypeEntryPrivate *d); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ComplexTypeEntry::TypeFlags) + +#endif // COMPLEXTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp b/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp new file mode 100644 index 000000000..b6eda651c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/conditionalstreamreader.cpp @@ -0,0 +1,211 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "conditionalstreamreader.h" + +#include <QtCore/QDebug> +#include <QtCore/QHash> + +using namespace Qt::StringLiterals; + +// ProxyEntityResolver proxies a QXmlStreamEntityResolver set by the user +// on ConditionalStreamReader and stores entity definitions from the +// <?entity name value?> processing instruction in a cache +// (which is also used for the proxied resolver). +class ProxyEntityResolver : public QXmlStreamEntityResolver +{ +public: + QString resolveEntity(const QString& publicId, + const QString& systemId) override; + QString resolveUndeclaredEntity(const QString &name) override; + + QXmlStreamEntityResolver *source() const { return m_source; } + void setSource(QXmlStreamEntityResolver *s) { m_source = s; } + + void defineEntity(const QString &name, const QString &value) + { + m_undeclaredEntityCache.insert(name, value); + } + +private: + QHash<QString, QString> m_undeclaredEntityCache; + QXmlStreamEntityResolver *m_source = nullptr; +}; + +QString ProxyEntityResolver::resolveEntity(const QString &publicId, const QString &systemId) +{ + QString result; + if (m_source != nullptr) + result = m_source->resolveEntity(publicId, systemId); + if (result.isEmpty()) + result = QXmlStreamEntityResolver::resolveEntity(publicId, systemId); + return result; +} + +QString ProxyEntityResolver::resolveUndeclaredEntity(const QString &name) +{ + const auto it = m_undeclaredEntityCache.constFind(name); + if (it != m_undeclaredEntityCache.constEnd()) + return it.value(); + if (m_source == nullptr) + return {}; + const QString result = m_source->resolveUndeclaredEntity(name); + if (!result.isEmpty()) + defineEntity(name, result); + return result; +} + +ConditionalStreamReader::ConditionalStreamReader(QIODevice *iod) : + m_reader(iod) +{ + init(); +} + +ConditionalStreamReader::ConditionalStreamReader(const QString &s) : + m_reader(s) +{ + init(); +} + +void ConditionalStreamReader::init() +{ + m_proxyEntityResolver = new ProxyEntityResolver; + m_reader.setEntityResolver(m_proxyEntityResolver); +} + +ConditionalStreamReader::~ConditionalStreamReader() +{ + m_reader.setEntityResolver(nullptr); + delete m_proxyEntityResolver; +} + +void ConditionalStreamReader::setEntityResolver(QXmlStreamEntityResolver *resolver) +{ + m_proxyEntityResolver->setSource(resolver); +} + +QXmlStreamEntityResolver *ConditionalStreamReader::entityResolver() const +{ + return m_proxyEntityResolver->source(); +} + +QXmlStreamReader::TokenType ConditionalStreamReader::readNext() +{ + auto exToken = readNextInternal(); + + if (exToken.second == PiTokens::EntityDefinition) + return readEntityDefinitonPi() ? exToken.first : QXmlStreamReader::Invalid; + + if (exToken.second != PiTokens::If || conditionMatches()) + return exToken.first; + + // Condition does not match - search for endif + int nestingLevel = 1; + while (true) { + exToken = readNextInternal(); + if (exToken.first == QXmlStreamReader::NoToken + || exToken.first == QXmlStreamReader::Invalid + || exToken.first == QXmlStreamReader::EndDocument) { + break; + } + + if (exToken.second == PiTokens::If) + ++nestingLevel; + else if (exToken.second == PiTokens::Endif && --nestingLevel == 0) + break; + } + return exToken.first; +} + +void ConditionalStreamReader::defineEntity(const QString &name, const QString &value) +{ + m_proxyEntityResolver->defineEntity(name, value); +} + +// Read an entity definition: "<?entity name value?>: +bool ConditionalStreamReader::readEntityDefinitonPi() +{ + const auto data = m_reader.processingInstructionData(); + const auto separator = data.indexOf(u' '); + if (separator <= 0 || separator == data.size() - 1) { + m_reader.raiseError(u"Malformed entity definition: "_s + data.toString()); + return false; + } + defineEntity(data.left(separator).toString(), + data.right(data.size() - separator - 1).toString()); + return true; +} + +bool ConditionalStreamReader::conditionMatches() const +{ + const auto keywords = m_reader.processingInstructionData().split(u' ', Qt::SkipEmptyParts); + if (keywords.isEmpty()) + return false; + + bool matches = false; + bool exclusionOnly = true; + for (const auto &keyword : keywords) { + if (keyword.startsWith(u'!')) { // exclusion '!windows' takes preference + if (m_conditions.contains(keyword.mid(1))) + return false; + } else { + exclusionOnly = false; + matches |= m_conditions.contains(keyword); + } + } + return exclusionOnly || matches; +} + +void ConditionalStreamReader::setConditions(const QStringList &newConditions) +{ + m_conditions = newConditions + platformConditions(); +} + +QStringList ConditionalStreamReader::platformConditions() +{ + QStringList result; +#if defined (Q_OS_UNIX) + result << "unix"_L1; +#endif + +#if defined (Q_OS_LINUX) + result << "linux"_L1; +#elif defined (Q_OS_MACOS) + result << "darwin"_L1; +#elif defined (Q_OS_WINDOWS) + result << "windows"_L1; +#endif + return result; +} + +ConditionalStreamReader::ExtendedToken ConditionalStreamReader::readNextInternal() +{ + const auto token = m_reader.readNext(); + PiTokens piToken = PiTokens::None; + if (token == QXmlStreamReader::ProcessingInstruction) { + const auto target = m_reader.processingInstructionTarget(); + if (target == u"if") + piToken = PiTokens::If; + else if (target == u"endif") + piToken = PiTokens::Endif; + else if (target == u"entity") + piToken = PiTokens::EntityDefinition; + } + return {token, piToken}; +} + +QDebug operator<<(QDebug dbg, const QXmlStreamAttributes &attrs) +{ + QDebugStateSaver saver(dbg); + dbg.noquote(); + dbg.nospace(); + dbg << "QXmlStreamAttributes("; + for (qsizetype i = 0, size = attrs.size(); i < size; ++i ) { + if (i) + dbg << ", "; + dbg << attrs.at(i).name() << "=\"" << attrs.at(i).value() << '"'; + } + dbg << ')'; + return dbg; +} + diff --git a/sources/shiboken6/ApiExtractor/conditionalstreamreader.h b/sources/shiboken6/ApiExtractor/conditionalstreamreader.h new file mode 100644 index 000000000..730697525 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/conditionalstreamreader.h @@ -0,0 +1,90 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONDITIONALSTREAMREADER_H +#define CONDITIONALSTREAMREADER_H + +#include <QtCore/QXmlStreamReader> + +#include <utility> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class ProxyEntityResolver; + +/// ConditionalStreamReader encapsulates QXmlStreamReader, offering the same +/// API (except readNextStartElement() and similar conveniences) and internally +/// uses Processing Instructions like: +/// <?if keyword1 !keyword2?> ... <?endif?> +/// to exclude/include sections depending on a list of condition keywords, +/// containing for example the OS. +/// It should be possible to use it as a drop-in replacement for +/// QXmlStreamReader for any parsing code based on readNext(). +/// It also allows for specifying entities using a Processing Instruction: +/// <?entity name value?> +/// which can be used in conjunction with conditional processing. +class ConditionalStreamReader +{ +public: + Q_DISABLE_COPY_MOVE(ConditionalStreamReader) + + using TokenType = QXmlStreamReader::TokenType; + explicit ConditionalStreamReader(QIODevice *iod); + explicit ConditionalStreamReader(const QString &s); + ~ConditionalStreamReader(); + + QIODevice *device() const { return m_reader.device(); } + + // Note: Caching of entity values is done internally by + // ConditionalStreamReader. + void setEntityResolver(QXmlStreamEntityResolver *resolver); + QXmlStreamEntityResolver *entityResolver() const; + + bool atEnd() const { return m_reader.atEnd(); } + TokenType readNext(); + + TokenType tokenType() const { return m_reader.tokenType(); } + + qint64 lineNumber() const { return m_reader.lineNumber(); } + qint64 columnNumber() const { return m_reader.columnNumber(); } + + QXmlStreamAttributes attributes() const { return m_reader.attributes(); } + + QString readElementText(QXmlStreamReader::ReadElementTextBehaviour behaviour = QXmlStreamReader::ErrorOnUnexpectedElement) + { return m_reader.readElementText(behaviour); } + + QStringView name() const { return m_reader.name(); } + QStringView qualifiedName() const { return m_reader.qualifiedName(); } + + QStringView text() const { return m_reader.text(); } + + QString errorString() const { return m_reader.errorString(); } + QXmlStreamReader::Error error() const { return m_reader.error(); } + + bool hasError() const { return m_reader.hasError(); } + + // Additional functions (not delegating to QXmlStreamReader) + void defineEntity(const QString &name, const QString &value); + + const QStringList &conditions() const { return m_conditions; } + void setConditions(const QStringList &newConditions); + + static QStringList platformConditions(); + +private: + enum class PiTokens { None, If, Endif, EntityDefinition }; + + using ExtendedToken = std::pair<TokenType, PiTokens>; + ExtendedToken readNextInternal(); + void init(); + bool conditionMatches() const; + bool readEntityDefinitonPi(); + + QXmlStreamReader m_reader; + ProxyEntityResolver *m_proxyEntityResolver = nullptr; + QStringList m_conditions = ConditionalStreamReader::platformConditions(); +}; + +QDebug operator<<(QDebug dbg, const QXmlStreamAttributes &a); + +#endif // CONDITIONALSTREAMREADER_H diff --git a/sources/shiboken6/ApiExtractor/configurabletypeentry.h b/sources/shiboken6/ApiExtractor/configurabletypeentry.h new file mode 100644 index 000000000..59522e16c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/configurabletypeentry.h @@ -0,0 +1,28 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONFIGURABLETYPEENTRY_H +#define CONFIGURABLETYPEENTRY_H + +#include "typesystem.h" + +class ConfigurableTypeEntryPrivate; + +class ConfigurableTypeEntry : public TypeEntry +{ +public: + explicit ConfigurableTypeEntry(const QString &entryName, Type t, + const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeEntry *clone() const override; + + QString configCondition() const; + void setConfigCondition(const QString &c); + bool hasConfigCondition() const; + +protected: + explicit ConfigurableTypeEntry(ConfigurableTypeEntryPrivate *d); +}; + +#endif // CONFIGURABLETYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/constantvaluetypeentry.h b/sources/shiboken6/ApiExtractor/constantvaluetypeentry.h new file mode 100644 index 000000000..a16a7ad12 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/constantvaluetypeentry.h @@ -0,0 +1,23 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONSTANTVALUETYPEENTRY_H +#define CONSTANTVALUETYPEENTRY_H + +#include "typesystem.h" + +// For primitive values, typically to provide a dummy type for +// example the '2' in non-type template 'Array<2>'. +class ConstantValueTypeEntry : public TypeEntry +{ +public: + explicit ConstantValueTypeEntry(const QString& name, + const TypeEntryCPtr &parent); + + TypeEntry *clone() const override; + +protected: + explicit ConstantValueTypeEntry(TypeEntryPrivate *d); +}; + +#endif // CONSTANTVALUETYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/containertypeentry.h b/sources/shiboken6/ApiExtractor/containertypeentry.h new file mode 100644 index 000000000..b2003816b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/containertypeentry.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONTAINERTYPEENTRY_H +#define CONTAINERTYPEENTRY_H + +#include "complextypeentry.h" +#include "customconversion_typedefs.h" + +class ContainerTypeEntryPrivate; + +struct OpaqueContainer // Generate an opaque container for an instantiation under name +{ + QStringList instantiations; + QString name; + + QString templateParameters() const; +}; + +using OpaqueContainers = QList<OpaqueContainer>; + +class ContainerTypeEntry : public ComplexTypeEntry +{ +public: + + enum ContainerKind { + ListContainer, + SetContainer, + MapContainer, + MultiMapContainer, + PairContainer, + SpanContainer, // Fixed size + }; + + explicit ContainerTypeEntry(const QString &entryName, ContainerKind containerKind, + const QVersionNumber &vr, const TypeEntryCPtr &parent); + + ContainerKind containerKind() const; + + /// Number of template parameters (except allocators) + qsizetype templateParameterCount() const; + + const OpaqueContainers &opaqueContainers() const; + void appendOpaqueContainers(const OpaqueContainers &l); + bool generateOpaqueContainer(const QStringList &instantiations) const; + QString opaqueContainerName(const QStringList &instantiations) const; + + bool hasCustomConversion() const; + void setCustomConversion(const CustomConversionPtr &customConversion); + CustomConversionPtr customConversion() const; + + TypeEntry *clone() const override; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif +protected: + explicit ContainerTypeEntry(ContainerTypeEntryPrivate *d); +}; + +QDebug operator<<(QDebug d, const OpaqueContainer &oc); + +#endif // CONTAINERTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/customconversion.cpp b/sources/shiboken6/ApiExtractor/customconversion.cpp new file mode 100644 index 000000000..4cfd1b974 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/customconversion.cpp @@ -0,0 +1,197 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "customconversion.h" +#include "containertypeentry.h" +#include "customtypenentry.h" +#include "primitivetypeentry.h" +#include "valuetypeentry.h" + +#include <QtCore/qdebug.h> + +using namespace Qt::StringLiterals; + +CustomConversion::CustomConversion(const TypeEntryCPtr &ownerType) : + m_ownerType(ownerType) +{ +} + +TypeEntryCPtr CustomConversion::ownerType() const +{ + return m_ownerType; +} + +QString CustomConversion::nativeToTargetConversion() const +{ + return m_nativeToTargetConversion; +} + +void CustomConversion::setNativeToTargetConversion(const QString &nativeToTargetConversion) +{ + m_nativeToTargetConversion = nativeToTargetConversion; +} + +bool CustomConversion::replaceOriginalTargetToNativeConversions() const +{ + return m_replaceOriginalTargetToNativeConversions; +} + +void CustomConversion::setReplaceOriginalTargetToNativeConversions(bool r) +{ + m_replaceOriginalTargetToNativeConversions = r; +} + +bool CustomConversion::hasTargetToNativeConversions() const +{ + return !(m_targetToNativeConversions.isEmpty()); +} + +TargetToNativeConversions &CustomConversion::targetToNativeConversions() +{ + return m_targetToNativeConversions; +} + +const TargetToNativeConversions &CustomConversion::targetToNativeConversions() const +{ + return m_targetToNativeConversions; +} + +void CustomConversion::addTargetToNativeConversion(const QString &sourceTypeName, + const QString &sourceTypeCheck, + const QString &conversion) +{ + m_targetToNativeConversions.append(TargetToNativeConversion(sourceTypeName, + sourceTypeCheck, + conversion)); +} + +TargetToNativeConversion::TargetToNativeConversion(const QString &sourceTypeName, + const QString &sourceTypeCheck, + const QString &conversion) : + m_sourceTypeName(sourceTypeName), m_sourceTypeCheck(sourceTypeCheck), + m_conversion(conversion) +{ +} + +TypeEntryCPtr TargetToNativeConversion::sourceType() const +{ + return m_sourceType; +} + +void TargetToNativeConversion::setSourceType(const TypeEntryCPtr &sourceType) +{ + m_sourceType = sourceType; +} + +bool TargetToNativeConversion::isCustomType() const +{ + return m_sourceType == nullptr; +} + +QString TargetToNativeConversion::sourceTypeName() const +{ + return m_sourceTypeName; +} + +QString TargetToNativeConversion::sourceTypeCheck() const +{ + if (!m_sourceTypeCheck.isEmpty()) + return m_sourceTypeCheck; + + if (m_sourceType != nullptr && m_sourceType->isCustom()) { + const auto cte = std::static_pointer_cast<const CustomTypeEntry>(m_sourceType); + if (cte->hasCheckFunction()) { + QString result = cte->checkFunction(); + if (result != u"true") // For PyObject, which is always true + result += u"(%in)"_s; + return result; + } + } + + return {}; +} + +QString TargetToNativeConversion::conversion() const +{ + return m_conversion; +} + +void TargetToNativeConversion::setConversion(const QString &conversion) +{ + m_conversion = conversion; +} + +void TargetToNativeConversion::formatDebug(QDebug &debug) const +{ + debug << "(source=\"" << m_sourceTypeName << '"'; + if (debug.verbosity() > 2) + debug << ", conversion=\"" << m_conversion << '"'; + if (isCustomType()) + debug << ", [custom]"; + debug << ')'; +} + +CustomConversionPtr CustomConversion::getCustomConversion(const TypeEntryCPtr &type) +{ + if (type->isPrimitive()) + return std::static_pointer_cast<const PrimitiveTypeEntry>(type)->customConversion(); + if (type->isContainer()) + return std::static_pointer_cast<const ContainerTypeEntry>(type)->customConversion(); + if (type->isValue()) + return std::static_pointer_cast<const ValueTypeEntry>(type)->customConversion(); + return {}; +} + +void CustomConversion::formatDebug(QDebug &debug) const +{ + debug << "(owner=\"" << m_ownerType->qualifiedCppName() << '"'; + if (!m_nativeToTargetConversion.isEmpty()) + debug << ", nativeToTargetConversion=\"" << m_nativeToTargetConversion << '"'; + if (!m_targetToNativeConversions.isEmpty()) { + debug << ", targetToNativeConversions=["; + for (qsizetype i = 0, size = m_targetToNativeConversions.size(); i < size; ++i) { + if (i) + debug << ", "; + debug << m_targetToNativeConversions.at(i); + + } + debug << ']'; + } + if (m_replaceOriginalTargetToNativeConversions) + debug << ", [replaceOriginalTargetToNativeConversions]"; + debug << ')'; +} + +QDebug operator<<(QDebug debug, const TargetToNativeConversion &t) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "TargetToNativeConversion"; + t.formatDebug(debug); + return debug; +} + +QDebug operator<<(QDebug debug, const CustomConversion &c) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "CustomConversion"; + c.formatDebug(debug); + return debug; +} + +QDebug operator<<(QDebug debug, const CustomConversionPtr &cptr) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "CustomConversionPtr"; + if (auto *c = cptr.get()) { + c->formatDebug(debug); + } else { + debug << "(0)"; + } + return debug; +} diff --git a/sources/shiboken6/ApiExtractor/customconversion.h b/sources/shiboken6/ApiExtractor/customconversion.h new file mode 100644 index 000000000..fd0a67759 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/customconversion.h @@ -0,0 +1,81 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CUSTOMCONVERSION_H +#define CUSTOMCONVERSION_H + +#include "customconversion_typedefs.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QList> +#include <QtCore/QString> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class TypeEntry; + +class TargetToNativeConversion +{ +public: + explicit TargetToNativeConversion(const QString &sourceTypeName, + const QString &sourceTypeCheck, + const QString &conversion = {}); + + TypeEntryCPtr sourceType() const; + void setSourceType(const TypeEntryCPtr &sourceType); + bool isCustomType() const; + QString sourceTypeName() const; + QString sourceTypeCheck() const; + QString conversion() const; + void setConversion(const QString &conversion); + + void formatDebug(QDebug &d) const; + +private: + TypeEntryCPtr m_sourceType; + QString m_sourceTypeName; + QString m_sourceTypeCheck; + QString m_conversion; +}; + +using TargetToNativeConversions = QList<TargetToNativeConversion>; + +class CustomConversion +{ +public: + explicit CustomConversion(const TypeEntryCPtr &ownerType); + + TypeEntryCPtr ownerType() const; + QString nativeToTargetConversion() const; + void setNativeToTargetConversion(const QString &nativeToTargetConversion); + + /// Returns true if the target to C++ custom conversions should + /// replace the original existing ones, and false if the custom + /// conversions should be added to the original. + bool replaceOriginalTargetToNativeConversions() const; + void setReplaceOriginalTargetToNativeConversions(bool r); + + bool hasTargetToNativeConversions() const; + TargetToNativeConversions &targetToNativeConversions(); + const TargetToNativeConversions &targetToNativeConversions() const; + void addTargetToNativeConversion(const QString &sourceTypeName, + const QString &sourceTypeCheck, + const QString &conversion = QString()); + + /// Return the custom conversion of a type; helper for type system parser + static CustomConversionPtr getCustomConversion(const TypeEntryCPtr &type); + + void formatDebug(QDebug &debug) const; + +private: + TypeEntryCPtr m_ownerType; + QString m_nativeToTargetConversion; + TargetToNativeConversions m_targetToNativeConversions; + bool m_replaceOriginalTargetToNativeConversions = false; +}; + +QDebug operator<<(QDebug debug, const TargetToNativeConversion &t); +QDebug operator<<(QDebug debug, const CustomConversion &c); +QDebug operator<<(QDebug debug, const CustomConversionPtr &cptr); + +#endif // CUSTOMCONVERSION_H diff --git a/sources/shiboken6/ApiExtractor/customconversion_typedefs.h b/sources/shiboken6/ApiExtractor/customconversion_typedefs.h new file mode 100644 index 000000000..6528f7d7b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/customconversion_typedefs.h @@ -0,0 +1,14 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CUSTOMCONVERSION_TYPEDEFS_H +#define CUSTOMCONVERSION_TYPEDEFS_H + +#include <QtCore/QList> + +#include <memory> + +class CustomConversion; +using CustomConversionPtr = std::shared_ptr<CustomConversion>; + +#endif // CUSTOMCONVERSION_TYPEDEFS_H diff --git a/sources/shiboken6/ApiExtractor/customtypenentry.h b/sources/shiboken6/ApiExtractor/customtypenentry.h new file mode 100644 index 000000000..a57bb858f --- /dev/null +++ b/sources/shiboken6/ApiExtractor/customtypenentry.h @@ -0,0 +1,30 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CUSTOMTYPENENTRY_H +#define CUSTOMTYPENENTRY_H + +#include "typesystem.h" + +class CustomTypeEntry : public TypeEntry +{ +public: + explicit CustomTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeEntry *clone() const override; + + bool hasCheckFunction() const; + QString checkFunction() const; + void setCheckFunction(const QString &f); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +protected: + explicit CustomTypeEntry(TypeEntryPrivate *d); +}; + + +#endif // CUSTOMTYPENENTRY_H diff --git a/sources/shiboken6/ApiExtractor/debughelpers_p.h b/sources/shiboken6/ApiExtractor/debughelpers_p.h new file mode 100644 index 000000000..81ebbb3b9 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/debughelpers_p.h @@ -0,0 +1,56 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEBUGHELPERS_P_H +#define DEBUGHELPERS_P_H + +#include <QtCore/QDebug> +#include <memory> + +template <class T> +inline QDebug operator<<(QDebug debug, const std::shared_ptr<T> &ptr) +{ + QDebugStateSaver saver(debug); + debug.nospace(); + debug << "std::shared_ptr(" << ptr.get() << ")"; + return debug; +} + +template <class It> +inline void formatSequence(QDebug &d, It i1, It i2, + const char *separator=", ") +{ + for (It i = i1; i != i2; ++i) { + if (i != i1) + d << separator; + d << *i; + } +} + +template <class It> +inline static void formatPtrSequence(QDebug &d, It i1, It i2, + const char *separator=", ") +{ + for (It i = i1; i != i2; ++i) { + if (i != i1) + d << separator; + d << i->get(); + } +} + +template <class Container> +static void formatList(QDebug &d, const char *name, const Container &c, + const char *separator=", ") +{ + if (const auto size = c.size()) { + d << ", " << name << '[' << size << "]=("; + for (qsizetype i = 0; i < size; ++i) { + if (i) + d << separator; + d << c.at(i); + } + d << ')'; + } +} + +#endif // DEBUGHELPERS_P_H diff --git a/sources/shiboken6/ApiExtractor/dependency.h b/sources/shiboken6/ApiExtractor/dependency.h new file mode 100644 index 000000000..aa280de03 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/dependency.h @@ -0,0 +1,22 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEPENDENCY_H +#define DEPENDENCY_H + +#include <QtCore/QList> + +#include <utility> + +// Dependencies for topologically sorting classes + +class AbstractMetaClass; + +struct Dependency { + AbstractMetaClassPtr parent; + AbstractMetaClassPtr child; +}; + +using Dependencies = QList<Dependency>; + +#endif // DEPENDENCY_H diff --git a/sources/shiboken6/ApiExtractor/docparser.cpp b/sources/shiboken6/ApiExtractor/docparser.cpp new file mode 100644 index 000000000..468fe1098 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/docparser.cpp @@ -0,0 +1,232 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "docparser.h" +#include "abstractmetaargument.h" +#include "abstractmetaenum.h" +#include "abstractmetafunction.h" +#include "abstractmetalang.h" +#include "abstractmetatype.h" +#include "messages.h" +#include "modifications.h" +#include "reporthandler.h" +#include "enumtypeentry.h" +#include "complextypeentry.h" +#include "xmlutils.h" + +#include <QtCore/QBuffer> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QTextStream> + +#include "qtcompat.h" + +#include <cstdlib> +#ifdef HAVE_LIBXSLT +# include <libxslt/xsltutils.h> +# include <libxslt/transform.h> +#endif + +#include <algorithm> + +using namespace Qt::StringLiterals; + +static inline bool isXpathDocModification(const DocModification &mod) +{ + return mod.mode() == TypeSystem::DocModificationXPathReplace; +} + +static inline bool isNotXpathDocModification(const DocModification &mod) +{ + return mod.mode() != TypeSystem::DocModificationXPathReplace; +} + +static void removeXpathDocModifications(DocModificationList *l) +{ + l->erase(std::remove_if(l->begin(), l->end(), isXpathDocModification), l->end()); +} + +static void removeNonXpathDocModifications(DocModificationList *l) +{ + l->erase(std::remove_if(l->begin(), l->end(), isNotXpathDocModification), l->end()); +} + +DocParser::DocParser() = default; +DocParser::~DocParser() = default; + +void DocParser::fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &) +{ +} + +void DocParser::fillGlobalEnumDocumentation(AbstractMetaEnum &) +{ +} + +QString DocParser::getDocumentation(const XQueryPtr &xquery, const QString& query, + const DocModificationList& mods) +{ + QString doc = execXQuery(xquery, query); + return applyDocModifications(mods, doc.trimmed()); +} + +QString DocParser::execXQuery(const XQueryPtr &xquery, const QString& query) +{ + QString errorMessage; + const QString result = xquery->evaluate(query, &errorMessage); + if (!errorMessage.isEmpty()) + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return result; +} + +static bool usesRValueReference(const AbstractMetaArgument &a) +{ + return a.type().referenceType() == RValueReference; +} + +bool DocParser::skipForQuery(const AbstractMetaFunctionCPtr &func) +{ + // Skip private functions and copies created by AbstractMetaClass::fixFunctions() + // Note: Functions inherited from templates will cause warnings about missing + // documentation, but they should at least be listed. + if (!func || func->isPrivate() + || func->attributes().testFlag(AbstractMetaFunction::AddedMethod) + || func->isModifiedRemoved() + || func->declaringClass() != func->ownerClass() + || func->isConversionOperator()) { + return true; + } + switch (func->functionType()) { + case AbstractMetaFunction::MoveConstructorFunction: + case AbstractMetaFunction::AssignmentOperatorFunction: + case AbstractMetaFunction::MoveAssignmentOperatorFunction: + return true; + default: + break; + } + + return std::any_of(func->arguments().cbegin(), func->arguments().cend(), + usesRValueReference); +} + +DocModificationList DocParser::getDocModifications(const AbstractMetaClassCPtr &cppClass) + +{ + auto result = cppClass->typeEntry()->docModifications(); + removeXpathDocModifications(&result); + return result; +} + +static void filterBySignature(const AbstractMetaFunctionCPtr &func, DocModificationList *l) +{ + if (!l->isEmpty()) { + const QString minimalSignature = func->minimalSignature(); + const auto filter = [&minimalSignature](const DocModification &mod) { + return mod.signature() != minimalSignature; + }; + l->erase(std::remove_if(l->begin(), l->end(), filter), l->end()); + } +} + +DocModificationList DocParser::getDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass) +{ + DocModificationList result; + if (func->isUserAdded()) { + result = func->addedFunctionDocModifications(); + removeXpathDocModifications(&result); + } else if (cppClass != nullptr) { + result = cppClass->typeEntry()->functionDocModifications(); + removeXpathDocModifications(&result); + filterBySignature(func, &result); + } + return result; +} + +DocModificationList DocParser::getXpathDocModifications(const AbstractMetaClassCPtr &cppClass) +{ + auto result = cppClass->typeEntry()->docModifications(); + removeNonXpathDocModifications(&result); + return result; +} + +DocModificationList DocParser::getXpathDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass) +{ + DocModificationList result; + if (func->isUserAdded()) { + result = func->addedFunctionDocModifications(); + removeNonXpathDocModifications(&result); + } else if (cppClass != nullptr) { + result = cppClass->typeEntry()->functionDocModifications(); + removeNonXpathDocModifications(&result); + filterBySignature(func, &result); + } + return result; +} + +QString DocParser::enumBaseClass(const AbstractMetaEnum &e) +{ + switch (e.typeEntry()->pythonEnumType()) { + case TypeSystem::PythonEnumType::IntEnum: + return u"IntEnum"_s; + case TypeSystem::PythonEnumType::Flag: + return u"Flag"_s; + case TypeSystem::PythonEnumType::IntFlag: + return u"IntFlag"_s; + default: + break; + } + return e.typeEntry()->flags() != nullptr ? u"Flag"_s : u"Enum"_s; +} + +AbstractMetaFunctionCList DocParser::documentableFunctions(const AbstractMetaClassCPtr &metaClass) +{ + auto result = metaClass->functionsInTargetLang(); + for (auto i = result.size() - 1; i >= 0; --i) { + if (DocParser::skipForQuery(result.at(i)) || result.at(i)->isUserAdded()) + result.removeAt(i); + } + result.append(metaClass->cppSignalFunctions()); + return result; +} + +QString DocParser::applyDocModifications(const DocModificationList& xpathMods, + const QString& xml) +{ + const char xslPrefix[] = +R"(<xsl:template match="/"> + <xsl:apply-templates /> +</xsl:template> +<xsl:template match="*"> +<xsl:copy> + <xsl:copy-of select="@*"/> + <xsl:apply-templates/> +</xsl:copy> +</xsl:template> +)"; + + if (xpathMods.isEmpty() || xml.isEmpty()) + return xml; + + QString xsl = QLatin1StringView(xslPrefix); + for (const DocModification &mod : xpathMods) { + Q_ASSERT(isXpathDocModification(mod)); + QString xpath = mod.xpath(); + xpath.replace(u'"', u"""_s); + xsl += "<xsl:template match=\""_L1 + xpath + "\">"_L1 + + mod.code() + "</xsl:template>\n"_L1; + } + + QString errorMessage; + const QString result = xsl_transform(xml, xsl, &errorMessage); + if (!errorMessage.isEmpty()) + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgXpathDocModificationError(xpathMods, errorMessage))); + if (result == xml) { + const QString message = u"Query did not result in any modifications to \""_s + + xml + u'"'; + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgXpathDocModificationError(xpathMods, message))); + } + return result; +} diff --git a/sources/shiboken6/ApiExtractor/docparser.h b/sources/shiboken6/ApiExtractor/docparser.h new file mode 100644 index 000000000..6d458b25a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/docparser.h @@ -0,0 +1,125 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef DOCPARSER_H +#define DOCPARSER_H + +#include "abstractmetalang_typedefs.h" +#include "modifications_typedefs.h" + +#include <QtCore/QString> + +#include <memory> + +class AbstractMetaClass; +class DocModification; +class Documentation; + +class XQuery; + +struct FunctionDocumentation; + +class DocParser +{ +public: + Q_DISABLE_COPY_MOVE(DocParser) + + using XQueryPtr = std::shared_ptr<XQuery>; + + DocParser(); + virtual ~DocParser(); + virtual void fillDocumentation(const AbstractMetaClassPtr &metaClass) = 0; + virtual void fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &f); + virtual void fillGlobalEnumDocumentation(AbstractMetaEnum &e); + + /** + * Process and retrieves documentation concerning the entire + * module or library. + * \return object containing module/library documentation information + */ + virtual Documentation retrieveModuleDocumentation() = 0; + + void setDocumentationDataDirectory(const QString& dir) + { + m_docDataDir = dir; + } + + /** + * Informs the location of the XML data generated by the tool + * (e.g.: DoxyGen, qdoc) used to extract the library's documentation + * comment. + * \return the path for the directory containing the XML data created + * from the library's documentation beign parsed. + */ + QString documentationDataDirectory() const + { + return m_docDataDir; + } + + void setLibrarySourceDirectory(const QString& dir) + { + m_libSourceDir = dir; + } + /** + * Informs the location of the library being parsed. The library + * source code is parsed for the documentation comments. + * \return the path for the directory containing the source code of + * the library beign parsed. + */ + QString librarySourceDirectory() const + { + return m_libSourceDir; + } + + void setPackageName(const QString& packageName) + { + m_packageName = packageName; + } + /** + * Retrieves the name of the package (or module or library) being parsed. + * \return the name of the package (module/library) being parsed + */ + QString packageName() const + { + return m_packageName; + } + + /** + * Process and retrieves documentation concerning the entire + * module or library. + * \param name module name + * \return object containing module/library documentation information + * \todo Merge with retrieveModuleDocumentation() on next ABI change. + */ + virtual Documentation retrieveModuleDocumentation(const QString& name) = 0; + + static bool skipForQuery(const AbstractMetaFunctionCPtr &func); + + /// Helper to return the documentation modifications for a class + /// or a member function. + static DocModificationList getDocModifications(const AbstractMetaClassCPtr &cppClass); + static DocModificationList getDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass = {}); + static DocModificationList getXpathDocModifications(const AbstractMetaClassCPtr &cppClass); + static DocModificationList getXpathDocModifications(const AbstractMetaFunctionCPtr &func, + const AbstractMetaClassCPtr &cppClass = {}); + + static QString enumBaseClass(const AbstractMetaEnum &e); + +protected: + static QString getDocumentation(const XQueryPtr &xquery, + const QString &query, + const DocModificationList &mods); + + static AbstractMetaFunctionCList documentableFunctions(const AbstractMetaClassCPtr &metaClass); + + static QString applyDocModifications(const DocModificationList &xpathMods, const QString &xml); + +private: + QString m_packageName; + QString m_docDataDir; + QString m_libSourceDir; + + static QString execXQuery(const XQueryPtr &xquery, const QString &query) ; +}; + +#endif // DOCPARSER_H diff --git a/sources/shiboken6/ApiExtractor/documentation.cpp b/sources/shiboken6/ApiExtractor/documentation.cpp new file mode 100644 index 000000000..33cf0e9fb --- /dev/null +++ b/sources/shiboken6/ApiExtractor/documentation.cpp @@ -0,0 +1,65 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "documentation.h" + +#include <QtCore/QDebug> + +Documentation::Documentation(const QString &detailed, + const QString &brief, + Format fmt) : + m_detailed(detailed.trimmed()), m_brief(brief.trimmed()), m_format(fmt) +{ +} + +bool Documentation::isEmpty() const +{ + return m_detailed.isEmpty() && m_brief.isEmpty(); +} + +Documentation::Format Documentation::format() const +{ + return m_format; +} + +void Documentation::setValue(const QString &value, Documentation::Type t) +{ + if (t == Brief) + setBrief(value); + else + setDetailed(value); +} + +void Documentation::setFormat(Documentation::Format f) +{ + m_format = f; +} + +void Documentation::setDetailed(const QString &detailed) +{ + m_detailed = detailed.trimmed(); +} + +void Documentation::setBrief(const QString &brief) +{ + m_brief = brief.trimmed(); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const Documentation &d) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Documentation("; + if (!d.isEmpty()) { + debug << "format=" << d.format(); + if (!d.brief().isEmpty()) + debug << ", brief=\"" << d.brief() << '"'; + if (!d.detailed().isEmpty()) + debug << ", detailed=\"" << d.detailed() << '"'; + } + debug << ')'; + return debug; +} +#endif // QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/documentation.h b/sources/shiboken6/ApiExtractor/documentation.h new file mode 100644 index 000000000..df9d5d614 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/documentation.h @@ -0,0 +1,65 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DOCUMENTATION_H +#define DOCUMENTATION_H + +#include <QtCore/QString> +#include <QtCore/QtCompare> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class Documentation +{ +public: + enum Format { + Native, // XML + Target // RST + }; + + enum Type { + Detailed, + Brief, + Last + }; + + Documentation() = default; + explicit Documentation(const QString &detailed, + const QString &brief, + Format fmt = Documentation::Native); + + bool isEmpty() const; + + void setValue(const QString& value, Type t = Documentation::Detailed); + + Documentation::Format format() const; + void setFormat(Format f); + + bool equals(const Documentation &rhs) const; + + const QString &detailed() const { return m_detailed; } + void setDetailed(const QString &detailed); + + bool hasBrief() const { return !m_brief.isEmpty(); } + const QString &brief() const { return m_brief; } + void setBrief(const QString &brief); + +private: + friend bool comparesEqual(const Documentation &lhs, + const Documentation &rhs) noexcept + { + return lhs.m_format == rhs.m_format && lhs.m_detailed == rhs.m_detailed + && lhs.m_brief == rhs.m_brief; + } + Q_DECLARE_EQUALITY_COMPARABLE(Documentation) + + QString m_detailed; + QString m_brief; + Format m_format = Documentation::Native; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug debug, const Documentation &); +#endif + +#endif // DOCUMENTATION_H diff --git a/sources/shiboken6/ApiExtractor/dotview.cpp b/sources/shiboken6/ApiExtractor/dotview.cpp new file mode 100644 index 000000000..0bd192257 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/dotview.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "dotview.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QProcess> +#include <QtCore/QTemporaryFile> + +using namespace Qt::StringLiterals; + +bool showDotGraph(const QString &name, const QString &graph) +{ + constexpr auto imageType = "jpg"_L1; + + // Write out the graph to a temporary file + QTemporaryFile dotFile(QDir::tempPath() + u'/' + name + u"_XXXXXX.dot"_s); + if (!dotFile.open()) { + qWarning("Cannot open temporary file: %s", qPrintable(dotFile.errorString())); + return false; + } + const QString tempDotFile = dotFile.fileName(); + dotFile.write(graph.toUtf8()); + dotFile.close(); + + // Convert to image using "dot" + const QString imageFile = tempDotFile.left(tempDotFile.size() - 3) + imageType; + QProcess process; + process.start(u"dot"_s, {u"-T"_s + imageType, u"-o"_s + imageFile, tempDotFile}); + if (!process.waitForStarted() || !process.waitForFinished()) { + qWarning("Image conversion failed: %s", qPrintable(process.errorString())); + return false; + } + if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) { + qWarning("Image conversion failed (%d): %s", + process.exitCode(), + process.readAllStandardError().constData()); + return false; + } + + // Launch image. Should use QDesktopServices::openUrl(), + // but we don't link against QtGui +#ifdef Q_OS_UNIX + constexpr auto imageViewer = "gwenview"_L1; +#else + constexpr auto imageViewer = "mspaint"_L1; +#endif + if (!QProcess::startDetached(imageViewer, {imageFile})) { + qWarning("Failed to launch viewer: %s", qPrintable(imageViewer)); + return false; + } + qInfo().noquote().nospace() << "Viewing: " + << QDir::toNativeSeparators(tempDotFile) + << ' ' << QDir::toNativeSeparators(imageFile); + return true; +} diff --git a/sources/shiboken6/ApiExtractor/dotview.h b/sources/shiboken6/ApiExtractor/dotview.h new file mode 100644 index 000000000..87fb7db65 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/dotview.h @@ -0,0 +1,14 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DOTVIEW_H +#define DOTVIEW_H + +#include <QtCore/QString> + +/// Show a dot digraph in an image viewer +/// \param name base name for files +/// \param graph graph +bool showDotGraph(const QString &name, const QString &graph); + +#endif // DOTVIEW_H diff --git a/sources/shiboken6/ApiExtractor/doxygenparser.cpp b/sources/shiboken6/ApiExtractor/doxygenparser.cpp new file mode 100644 index 000000000..da790015f --- /dev/null +++ b/sources/shiboken6/ApiExtractor/doxygenparser.cpp @@ -0,0 +1,224 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "doxygenparser.h" +#include "abstractmetaargument.h" +#include "abstractmetalang.h" +#include "abstractmetafield.h" +#include "abstractmetafunction.h" +#include "abstractmetaenum.h" +#include "abstractmetatype.h" +#include "documentation.h" +#include "messages.h" +#include "modifications.h" +#include "propertyspec.h" +#include "reporthandler.h" +#include "complextypeentry.h" +#include "xmlutils.h" + +#include "qtcompat.h" + +#include <QtCore/QFile> +#include <QtCore/QDir> + +using namespace Qt::StringLiterals; + +static QString getSectionKindAttr(const AbstractMetaFunctionCPtr &func) +{ + if (func->isSignal()) + return u"signal"_s; + QString kind = func->isPublic() + ? u"public"_s : u"protected"_s; + if (func->isStatic()) + kind += u"-static"_s; + else if (func->isSlot()) + kind += u"-slot"_s; + return kind; +} + +Documentation DoxygenParser::retrieveModuleDocumentation() +{ + return retrieveModuleDocumentation(packageName()); +} + +void DoxygenParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) +{ + if (!metaClass) + return; + + QString doxyFileSuffix; + if (metaClass->enclosingClass()) { + doxyFileSuffix += metaClass->enclosingClass()->name(); + doxyFileSuffix += u"_1_1"_s; // FIXME: Check why _1_1!! + } + doxyFileSuffix += metaClass->name(); + doxyFileSuffix += u".xml"_s; + + static constexpr QLatin1StringView prefixes[] = { "class"_L1, "struct"_L1, "namespace"_L1 }; + bool isProperty = false; + + QString doxyFilePath; + for (const auto &prefix : prefixes) { + doxyFilePath = documentationDataDirectory() + u'/' + prefix + doxyFileSuffix; + if (QFile::exists(doxyFilePath)) + break; + doxyFilePath.clear(); + } + + if (doxyFilePath.isEmpty()) { + qCWarning(lcShibokenDoc).noquote().nospace() + << "Can't find doxygen file for class " << metaClass->name() << ", tried: " + << QDir::toNativeSeparators(documentationDataDirectory()) + << "/{struct|class|namespace}"<< doxyFileSuffix; + return; + } + + QString errorMessage; + XQueryPtr xquery = XQuery::create(doxyFilePath, &errorMessage); + if (!xquery) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return; + } + + static const QList<std::pair<Documentation::Type, QString>> docTags = { + { Documentation::Brief, u"briefdescription"_s }, + { Documentation::Detailed, u"detaileddescription"_s } + }; + // Get class documentation + Documentation classDoc; + + for (const auto &tag : docTags) { + const QString classQuery = u"/doxygen/compounddef/"_s + tag.second; + QString doc = getDocumentation(xquery, classQuery, + metaClass->typeEntry()->docModifications()); + if (doc.isEmpty()) + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(doxyFilePath, "class", metaClass->name(), + classQuery))); + else + classDoc.setValue(doc, tag.first); + } + metaClass->setDocumentation(classDoc); + + //Functions Documentation + const auto &funcs = DocParser::documentableFunctions(metaClass); + for (const auto &func : funcs) { + QString query = u"/doxygen/compounddef/sectiondef"_s; + // properties + if (func->isPropertyReader() || func->isPropertyWriter() + || func->isPropertyResetter()) { + const auto prop = metaClass->propertySpecs().at(func->propertySpecIndex()); + query += u"[@kind=\"property\"]/memberdef/name[text()=\""_s + + prop.name() + u"\"]"_s; + isProperty = true; + } else { // normal methods + QString kind = getSectionKindAttr(func); + query += u"[@kind=\""_s + kind + + u"-func\"]/memberdef/name[text()=\""_s + + func->originalName() + u"\"]"_s; + + if (func->arguments().isEmpty()) { + QString args = func->isConstant() ? u"() const"_s : u"()"_s; + query += u"/../argsstring[text()=\""_s + args + u"\"]"_s; + } else { + int i = 1; + const AbstractMetaArgumentList &arguments = func->arguments(); + for (const AbstractMetaArgument &arg : arguments) { + if (!arg.type().isPrimitive()) { + query += u"/../param["_s + QString::number(i) + + u"]/type/ref[text()=\""_s + + arg.type().cppSignature().toHtmlEscaped() + + u"\"]/../.."_s; + } else { + query += u"/../param["_s + QString::number(i) + + u"]/type[text(), \""_s + + arg.type().cppSignature().toHtmlEscaped() + + u"\"]/.."_s; + } + ++i; + } + } + } + Documentation funcDoc; + for (const auto &tag : docTags) { + QString funcQuery(query); + if (!isProperty) { + funcQuery += u"/../"_s + tag.second; + } else { + funcQuery = u'(' + funcQuery; + funcQuery += u"/../"_s + tag.second + u")[1]"_s; + } + + QString doc = getDocumentation(xquery, funcQuery, + DocParser::getXpathDocModifications(func, metaClass)); + if (doc.isEmpty()) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(doxyFilePath, func.get(), + funcQuery))); + } else { + funcDoc.setValue(doc, tag.first); + } + } + std::const_pointer_cast<AbstractMetaFunction>(func)->setDocumentation(funcDoc); + isProperty = false; + } + + //Fields + for (AbstractMetaField &field : metaClass->fields()) { + if (field.isPrivate()) + return; + + Documentation fieldDoc; + for (const auto &tag : docTags) { + QString query = u"/doxygen/compounddef/sectiondef/memberdef/name[text()=\""_s + + field.name() + u"\"]/../"_s + tag.second; + QString doc = getDocumentation(xquery, query, DocModificationList()); + if (doc.isEmpty()) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(doxyFilePath, metaClass, field, + query))); + } else { + fieldDoc.setValue(doc, tag.first); + } + } + field.setDocumentation(fieldDoc); + } + + //Enums + for (AbstractMetaEnum &meta_enum : metaClass->enums()) { + QString query = u"/doxygen/compounddef/sectiondef/memberdef[@kind=\"enum\"]/name[text()=\""_s + + meta_enum.name() + u"\"]/.."_s; + QString doc = getDocumentation(xquery, query, DocModificationList()); + if (doc.isEmpty()) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(doxyFilePath, metaClass, meta_enum, query))); + } + meta_enum.setDocumentation(Documentation(doc, {})); + } + +} + +Documentation DoxygenParser::retrieveModuleDocumentation(const QString& name){ + + QString sourceFile = documentationDataDirectory() + u"/indexpage.xml"_s; + + if (!QFile::exists(sourceFile)) { + qCWarning(lcShibokenDoc).noquote().nospace() + << "Can't find doxygen XML file for module " << name << ", tried: " + << QDir::toNativeSeparators(sourceFile); + return {}; + } + + QString errorMessage; + XQueryPtr xquery = XQuery::create(sourceFile, &errorMessage); + if (!xquery) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return {}; + } + + // Module documentation + QString query = u"/doxygen/compounddef/detaileddescription"_s; + const QString doc = getDocumentation(xquery, query, DocModificationList()); + return Documentation(doc, {}); +} + diff --git a/sources/shiboken6/ApiExtractor/doxygenparser.h b/sources/shiboken6/ApiExtractor/doxygenparser.h new file mode 100644 index 000000000..4f6a9e53c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/doxygenparser.h @@ -0,0 +1,18 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DOXYGENPARSER_H +#define DOXYGENPARSER_H + +#include "docparser.h" + +class DoxygenParser : public DocParser +{ +public: + DoxygenParser() = default; + void fillDocumentation(const AbstractMetaClassPtr &metaClass) override; + Documentation retrieveModuleDocumentation() override; + Documentation retrieveModuleDocumentation(const QString& name) override; +}; + +#endif // DOXYGENPARSER_H diff --git a/sources/shiboken6/ApiExtractor/enclosingclassmixin.cpp b/sources/shiboken6/ApiExtractor/enclosingclassmixin.cpp new file mode 100644 index 000000000..2421ae527 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/enclosingclassmixin.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "enclosingclassmixin.h" +#include "abstractmetalang.h" +#include "namespacetypeentry.h" + +AbstractMetaClassCPtr EnclosingClassMixin::targetLangEnclosingClass() const +{ + auto result = m_enclosingClass.lock(); + while (result && !NamespaceTypeEntry::isVisibleScope(result->typeEntry())) + result = result->enclosingClass(); + return result; +} diff --git a/sources/shiboken6/ApiExtractor/enclosingclassmixin.h b/sources/shiboken6/ApiExtractor/enclosingclassmixin.h new file mode 100644 index 000000000..8d735d5ec --- /dev/null +++ b/sources/shiboken6/ApiExtractor/enclosingclassmixin.h @@ -0,0 +1,24 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ENCLOSINGCLASSMIXIN_H +#define ENCLOSINGCLASSMIXIN_H + +#include "abstractmetalang_typedefs.h" + +class AbstractMetaClass; + +class EnclosingClassMixin { +public: + + const AbstractMetaClassCPtr enclosingClass() const + { return m_enclosingClass.lock(); } + void setEnclosingClass(const AbstractMetaClassCPtr &cls) + { m_enclosingClass = cls; } + AbstractMetaClassCPtr targetLangEnclosingClass() const; + +private: + std::weak_ptr<const AbstractMetaClass> m_enclosingClass; +}; + +#endif // ENCLOSINGCLASSMIXIN_H diff --git a/sources/shiboken6/ApiExtractor/enumtypeentry.h b/sources/shiboken6/ApiExtractor/enumtypeentry.h new file mode 100644 index 000000000..3360d7db5 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/enumtypeentry.h @@ -0,0 +1,51 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ENUMTYPEENTRY_H +#define ENUMTYPEENTRY_H + +#include "configurabletypeentry.h" +#include "typesystem_enums.h" + +class EnumTypeEntryPrivate; + +// EnumTypeEntry is configurable for global enums only +class EnumTypeEntry : public ConfigurableTypeEntry +{ +public: + explicit EnumTypeEntry(const QString &entryName, + const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeSystem::PythonEnumType pythonEnumType() const; + void setPythonEnumType(TypeSystem::PythonEnumType t); + + QString targetLangQualifier() const; + + QString qualifier() const; + + EnumValueTypeEntryCPtr nullValue() const; + void setNullValue(const EnumValueTypeEntryCPtr &n); + + void setFlags(const FlagsTypeEntryPtr &flags); + FlagsTypeEntryPtr flags() const; + + QString cppType() const; + void setCppType(const QString &t); + + bool isEnumValueRejected(const QString &name) const; + void addEnumValueRejection(const QString &name); + QStringList enumValueRejections() const; + + QString docFile() const; + void setDocFile(const QString &df); + + TypeEntry *clone() const override; +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif +protected: + explicit EnumTypeEntry(EnumTypeEntryPrivate *d); +}; + +#endif // ENUMTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/enumvaluetypeentry.h b/sources/shiboken6/ApiExtractor/enumvaluetypeentry.h new file mode 100644 index 000000000..006b84e0a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/enumvaluetypeentry.h @@ -0,0 +1,31 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ENUMVALUETYPEENTRY_H +#define ENUMVALUETYPEENTRY_H + +#include "typesystem.h" + +class EnumTypeEntry; +class EnumValueTypeEntryPrivate; + +// EnumValueTypeEntry is used for resolving integer type templates +// like array<EnumValue>. Note: Dummy entries for integer values will +// be created for non-type template parameters, where m_enclosingEnum==nullptr. +class EnumValueTypeEntry : public TypeEntry +{ +public: + explicit EnumValueTypeEntry(const QString& name, const QString& value, + const EnumTypeEntryCPtr &enclosingEnum, + bool isScopedEnum, const QVersionNumber &vr); + + QString value() const; + EnumTypeEntryCPtr enclosingEnum() const; + + TypeEntry *clone() const override; + +protected: + explicit EnumValueTypeEntry(EnumValueTypeEntryPrivate *d); +}; + +#endif // ENUMVALUETYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/exception.h b/sources/shiboken6/ApiExtractor/exception.h new file mode 100644 index 000000000..396b56f5d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/exception.h @@ -0,0 +1,27 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXCEPTION_H +#define EXCEPTION_H + +#include <QtCore/QString> + +#include <string> +#include <exception> + +class Exception : public std::exception +{ +public: + explicit Exception(const QString &message) : m_message(message.toUtf8()) {} + explicit Exception(const char *message) : m_message(message) {} + + const char *what() const noexcept override + { + return m_message.c_str(); + } + +private: + const std::string m_message; +}; + +#endif // EXCEPTION diff --git a/sources/shiboken6/ApiExtractor/fileout.cpp b/sources/shiboken6/ApiExtractor/fileout.cpp new file mode 100644 index 000000000..6f9ec4d8a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/fileout.cpp @@ -0,0 +1,198 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "fileout.h" +#include "messages.h" +#include "reporthandler.h" +#include "exception.h" + +#include <QtCore/QFileInfo> +#include <QtCore/QDir> +#include <QtCore/QDebug> + +#include <cstdio> + +bool FileOut::m_dryRun = false; +bool FileOut::m_diff = false; + +#ifdef Q_OS_LINUX +static const char colorDelete[] = "\033[31m"; +static const char colorAdd[] = "\033[32m"; +static const char colorInfo[] = "\033[36m"; +static const char colorReset[] = "\033[0m"; +#else +static const char colorDelete[] = ""; +static const char colorAdd[] = ""; +static const char colorInfo[] = ""; +static const char colorReset[] = ""; +#endif + +FileOut::FileOut(QString n) : + stream(&m_buffer), + m_name(std::move(n)), + m_isDone(false) +{ +} + +FileOut::~FileOut() +{ + if (!m_isDone) { + qCWarning(lcShiboken).noquote().nospace() << __FUNCTION__ + << " file " << m_name << " not written."; + } +} + +static QList<qsizetype> lcsLength(const QByteArrayList &a, const QByteArrayList &b) +{ + const auto height = a.size() + 1; + const auto width = b.size() + 1; + + QList<qsizetype> res(width * height, 0); + + for (qsizetype row = 1; row < height; row++) { + for (qsizetype col = 1; col < width; col++) { + if (a.at(row - 1) == b.at(col - 1)) + res[width * row + col] = res[width * (row - 1) + col - 1] + 1; + else + res[width * row + col] = qMax(res[width * row + col - 1], + res[width * (row - 1) + col]); + } + } + return res; +} + +enum Type { + Add, + Delete, + Unchanged +}; + +struct Unit +{ + Type type; + qsizetype start; + qsizetype end; + + void print(const QByteArrayList &a, const QByteArrayList &b) const; +}; + +void Unit::print(const QByteArrayList &a, const QByteArrayList &b) const +{ + switch (type) { + case Unchanged: + if ((end - start) > 9) { + for (auto i = start; i <= start + 2; ++i) + std::printf(" %s\n", a.at(i).constData()); + std::printf("%s=\n= %d more lines\n=%s\n", + colorInfo, int(end - start - 6), colorReset); + for (auto i = end - 2; i <= end; ++i) + std::printf(" %s\n", a.at(i).constData()); + } else { + for (auto i = start; i <= end; ++i) + std::printf(" %s\n", a.at(i).constData()); + } + break; + case Add: + std::fputs(colorAdd, stdout); + for (auto i = start; i <= end; ++i) + std::printf("+ %s\n", b.at(i).constData()); + std::fputs(colorReset, stdout); + break; + case Delete: + std::fputs(colorDelete, stdout); + for (auto i = start; i <= end; ++i) + std::printf("- %s\n", a.at(i).constData()); + std::fputs(colorReset, stdout); + break; + } +} + +static void unitAppend(Type type, qsizetype pos, QList<Unit> *units) +{ + if (!units->isEmpty() && units->last().type == type) + units->last().end = pos; + else + units->append(Unit{type, pos, pos}); +} + +static QList<Unit> diffHelper(const QList<qsizetype> &lcs, + const QByteArrayList &a, const QByteArrayList &b, + qsizetype row, qsizetype col) +{ + if (row > 0 && col > 0 && a.at(row - 1) == b.at(col - 1)) { + QList<Unit> result = diffHelper(lcs, a, b, row - 1, col - 1); + unitAppend(Unchanged, row - 1, &result); + return result; + } + + const auto width = b.size() + 1; + if (col > 0 + && (row == 0 || lcs.at(width * row + col -1 ) >= lcs.at(width * (row - 1) + col))) { + QList<Unit> result = diffHelper(lcs, a, b, row, col - 1); + unitAppend(Add, col - 1, &result); + return result; + } + if (row > 0 + && (col == 0 || lcs.at(width * row + col-1) < lcs.at(width * (row - 1) + col))) { + QList<Unit> result = diffHelper(lcs, a, b, row - 1, col); + unitAppend(Delete, row - 1, &result); + return result; + } + return {}; +} + +static void diff(const QByteArrayList &a, const QByteArrayList &b) +{ + const QList<Unit> res = diffHelper(lcsLength(a, b), a, b, a.size(), b.size()); + for (const Unit &unit : res) + unit.print(a, b); +} + +FileOut::State FileOut::done() +{ + if (m_isDone) + return Success; + + bool fileEqual = false; + QFile fileRead(m_name); + QFileInfo info(fileRead); + stream.flush(); + QByteArray original; + if (info.exists() && (m_diff || (info.size() == m_buffer.size()))) { + if (!fileRead.open(QIODevice::ReadOnly)) + throw Exception(msgCannotOpenForReading(fileRead)); + + original = fileRead.readAll(); + fileRead.close(); + fileEqual = (original == m_buffer); + } + + if (fileEqual) { + m_isDone = true; + return Unchanged; + } + + if (!FileOut::m_dryRun) { + QDir dir(info.absolutePath()); + if (!dir.mkpath(dir.absolutePath())) { + const QString message = QString::fromLatin1("Unable to create directory '%1'") + .arg(QDir::toNativeSeparators(dir.absolutePath())); + throw Exception(message); + } + + QFile fileWrite(m_name); + if (!fileWrite.open(QIODevice::WriteOnly)) + throw Exception(msgCannotOpenForWriting(fileWrite)); + if (fileWrite.write(m_buffer) == -1 || !fileWrite.flush()) + throw Exception(msgWriteFailed(fileWrite, m_buffer.size())); + } + if (m_diff) { + std::printf("%sFile: %s%s\n", colorInfo, qPrintable(m_name), colorReset); + ::diff(original.split('\n'), m_buffer.split('\n')); + std::printf("\n"); + } + + m_isDone = true; + + return Success; +} diff --git a/sources/shiboken6/ApiExtractor/fileout.h b/sources/shiboken6/ApiExtractor/fileout.h new file mode 100644 index 000000000..b11ad1e20 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/fileout.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FILEOUT_H +#define FILEOUT_H + +#include "textstream.h" + +class Exception; + +QT_FORWARD_DECLARE_CLASS(QFile) + +class FileOut +{ + QByteArray m_buffer; +public: + Q_DISABLE_COPY_MOVE(FileOut) + + enum State { Unchanged, Success }; + + explicit FileOut(QString name); + ~FileOut(); + + QString filePath() const { return m_name; } + + State done() noexcept(false); + + TextStream stream; + + static bool diff() { return m_diff; } + static void setDiff(bool diff) { m_diff = diff; } + + static bool dryRun() { return m_dryRun; } + static void setDryRun(bool dryRun) { m_dryRun = dryRun; } + +private: + QString m_name; + bool m_isDone; + static bool m_dryRun; + static bool m_diff; +}; + +#endif // FILEOUT_H diff --git a/sources/shiboken6/ApiExtractor/flagstypeentry.h b/sources/shiboken6/ApiExtractor/flagstypeentry.h new file mode 100644 index 000000000..6eddcd12b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/flagstypeentry.h @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FLAGSTYPEENTRY_H +#define FLAGSTYPEENTRY_H + +#include "typesystem.h" + +class EnumTypeEntry; +class FlagsTypeEntryPrivate; + +// FlagsTypeEntry is configurable for global flags only +class FlagsTypeEntry : public TypeEntry +{ +public: + explicit FlagsTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + QString originalName() const; + void setOriginalName(const QString &s); + + QString flagsName() const; + void setFlagsName(const QString &name); + + EnumTypeEntryPtr originator() const; + void setOriginator(const EnumTypeEntryPtr &e); + + TypeEntry *clone() const override; + +protected: + explicit FlagsTypeEntry(FlagsTypeEntryPrivate *d); + + QString buildTargetLangName() const override; +}; + +#endif // FLAGSTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/functiontypeentry.h b/sources/shiboken6/ApiExtractor/functiontypeentry.h new file mode 100644 index 000000000..53aa1fad6 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/functiontypeentry.h @@ -0,0 +1,35 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FUNCTIONTYPEENTRY_H +#define FUNCTIONTYPEENTRY_H + +#include "typesystem.h" + +class FunctionTypeEntryPrivate; + +class FunctionTypeEntry : public TypeEntry +{ +public: + explicit FunctionTypeEntry(const QString& name, const QString& signature, + const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + const QStringList &signatures() const; + bool hasSignature(const QString& signature) const; + void addSignature(const QString& signature); + + QString docFile() const; + void setDocFile(const QString &df); + + TypeEntry *clone() const override; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +protected: + explicit FunctionTypeEntry(FunctionTypeEntryPrivate *d); +}; + +#endif // FUNCTIONTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/graph.h b/sources/shiboken6/ApiExtractor/graph.h new file mode 100644 index 000000000..447a26da0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/graph.h @@ -0,0 +1,321 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GRAPH_H +#define GRAPH_H + +#include "dotview.h" + +#include <QtCore/QDebug> +#include <QtCore/QFile> +#include <QtCore/QHash> +#include <QtCore/QList> +#include <QtCore/QString> +#include <QtCore/QTextStream> + +#include <algorithm> + +/// Result of topologically sorting of a graph (list of nodes in order +/// or list of nodes that have cyclic dependencies). +template <class Node> +struct GraphSortResult +{ + using NodeList = QList<Node>; + + bool isValid() const { return !result.isEmpty() && cyclic.isEmpty(); } + void format(QDebug &debug) const; + + NodeList result; + NodeList cyclic; +}; + +/// A graph that can have its nodes topologically sorted. The nodes need to +/// have operator==(). +template <class Node> +class Graph +{ +public: + using NodeList = QList<Node>; + + Graph() = default; + + // Construct from a sequence of nodes + template<class It> + explicit Graph(It i1, It i2) { setNodes(i1, i2); } + + template<class It> + void setNodes(It i1, It i2) + { + for (; i1 != i2; ++i1) + addNode(*i1); + } + + bool addNode(Node n); + + /// Returns whether node was registered + bool hasNode(Node node) { return indexOfNode(node) != -1; } + + /// Returns the numbed of nodes in this graph. + qsizetype nodeCount() const { return m_nodeEntries.size(); } + + /// Returns true if the graph contains the edge from -> to + bool containsEdge(Node from, Node to) const; + /// Returns true if the graph has any edges + bool hasEdges() const; + + /// Adds an edge to this graph. + bool addEdge(Node from, Node to); + /// Removes an edge from this graph. + bool removeEdge(Node from, Node to); + /// Clears the graph + void clear() { m_nodeEntries.clear(); } + + /// Dumps a dot graph to a file named \p filename. + /// \param fileName file name where the output should be written. + /// \param f function returning the name of a node + template <class NameFunction> + bool dumpDot(const QString& fileName, NameFunction f) const; + template <class NameFunction> + void formatDot(QTextStream &str, NameFunction f) const; + template <class NameFunction> + bool showGraph(const QString &name, NameFunction f) const; + + void format(QDebug &debug) const; + + /** + * Topologically sort this graph. + * \return A collection with all nodes topologically sorted or an empty collection if a cyclic + * dependency was found. + */ + GraphSortResult<Node> topologicalSort() const; + +private: + enum Color { WHITE, GRAY, BLACK }; + + struct NodeEntry + { + Node node; + NodeList targets; + mutable Color color; + }; + + Color colorAt(qsizetype i) const { return m_nodeEntries.at(i).color; } + qsizetype indexOfNode(Node n) const; + void depthFirstVisit(qsizetype i, NodeList &result) const; + + QList<NodeEntry> m_nodeEntries; +}; + +template <class Node> +qsizetype Graph<Node>::indexOfNode(Node n) const +{ + for (qsizetype i = 0, size = m_nodeEntries.size(); i < size; ++i) { + if (m_nodeEntries.at(i).node == n) + return i; + } + return -1; +} + +template <class Node> +bool Graph<Node>::addNode(Node n) +{ + if (hasNode(n)) + return false; + m_nodeEntries.append({n, {}, WHITE}); + return true; +} + +template <class Node> +void Graph<Node>::depthFirstVisit(qsizetype i, NodeList &result) const +{ + m_nodeEntries[i].color = GRAY; + + for (auto to : m_nodeEntries[i].targets) { + const qsizetype toIndex = indexOfNode(to); + Q_ASSERT(toIndex != -1); + switch (colorAt(toIndex)) { + case WHITE: + depthFirstVisit(toIndex, result); + break; + case GRAY: + return; // This is not a DAG! + case BLACK: + break; + } + } + + m_nodeEntries[i].color = BLACK; + + result.append(m_nodeEntries.at(i).node); +} + +template <class Node> +GraphSortResult<Node> Graph<Node>::topologicalSort() const +{ + const qsizetype size = m_nodeEntries.size(); + + GraphSortResult<Node> result; + result.result.reserve(size); + + if (hasEdges()) { + for (qsizetype i = 0; i < size; ++i) + m_nodeEntries[i].color = WHITE; + for (qsizetype i = 0; i < size; ++i) { + if (colorAt(i) == WHITE) // recursive calls may have set it to black + depthFirstVisit(i, result.result); + } + } else { // no edges, shortcut + std::transform(m_nodeEntries.cbegin(), m_nodeEntries.cend(), + std::back_inserter(result.result), + [](const NodeEntry &ne) { return ne.node; }); + } + + if (result.result.size() == size) { + std::reverse(result.result.begin(), result.result.end()); + } else { + for (const auto &nodeEntry : m_nodeEntries) { + if (!result.result.contains(nodeEntry.node)) + result.cyclic.append(nodeEntry.node); + } + result.result.clear(); // Not a DAG! + } + return result; +} + +template <class Node> +bool Graph<Node>::containsEdge(Node from, Node to) const +{ + const qsizetype i = indexOfNode(from); + return i != -1 && m_nodeEntries.at(i).targets.contains(to); +} + +template <class Node> +bool Graph<Node>::hasEdges() const +{ + for (const auto &nodeEntry : m_nodeEntries) { + if (!nodeEntry.targets.isEmpty()) + return true; + } + return false; +} + +template <class Node> +bool Graph<Node>::addEdge(Node from, Node to) +{ + const qsizetype i = indexOfNode(from); + if (i == -1 || !hasNode(to) || m_nodeEntries.at(i).targets.contains(to)) + return false; + m_nodeEntries[i].targets.append(to); + return true; +} + +template <class Node> +bool Graph<Node>::removeEdge(Node from, Node to) +{ + const qsizetype i = indexOfNode(from); + if (i == -1) + return false; + auto &targets = m_nodeEntries[i].targets; + const qsizetype toIndex = targets.indexOf(to); + if (toIndex == -1) + return false; + targets.removeAt(toIndex); + return true; +} + +template <class Node> +template <class NameFunction> +bool Graph<Node>::dumpDot(const QString& fileName, + NameFunction nameFunction) const +{ + QFile output(fileName); + if (!output.open(QIODevice::WriteOnly)) + return false; + QTextStream s(&output); + formatDot(s, nameFunction); + return true; +} + +template <class Node> +template <class NameFunction> +void Graph<Node>::formatDot(QTextStream &s, + NameFunction nameFunction) const +{ + s << "digraph D {\n"; + for (const auto &nodeEntry : m_nodeEntries) { + if (!nodeEntry.targets.isEmpty()) { + const QString fromName = nameFunction(nodeEntry.node); + for (const Node &to : nodeEntry.targets) + s << '"' << fromName << "\" -> \"" << nameFunction(to) << "\"\n"; + } + } + s << "}\n"; +} + +template <class Node> +template <class NameFunction> +bool Graph<Node>::showGraph(const QString &name, NameFunction f) const +{ + QString graph; + QTextStream s(&graph); + formatDot(s, f); + return showDotGraph(name, graph); +} + +template <class Node> +void Graph<Node>::format(QDebug &debug) const +{ + const qsizetype size = m_nodeEntries.size(); + debug << "nodes[" << size << "] = ("; + for (qsizetype i = 0; i < size; ++i) { + const auto &nodeEntry = m_nodeEntries.at(i); + if (i) + debug << ", "; + debug << nodeEntry.node; + if (!nodeEntry.targets.isEmpty()) { + debug << " -> ["; + for (qsizetype t = 0, tsize = nodeEntry.targets.size(); t < tsize; ++t) { + if (t) + debug << ", "; + debug << nodeEntry.targets.at(t); + } + debug << ']'; + } + } + debug << ')'; +} + +template <class Node> +QDebug operator<<(QDebug debug, const Graph<Node> &g) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Graph("; + g.format(debug); + debug << ')'; + return debug; +} + +template <class Node> +void GraphSortResult<Node>::format(QDebug &debug) const +{ + if (isValid()) + debug << "Valid, " << result; + else + debug << "Invalid, cyclic dependencies: " << cyclic; +} + +template <class Node> +QDebug operator<<(QDebug debug, const GraphSortResult<Node> &r) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Graph::SortResult("; + r.format(debug); + debug << ')'; + return debug; +} + +#endif // GRAPH_H diff --git a/sources/shiboken6/ApiExtractor/header_paths.h b/sources/shiboken6/ApiExtractor/header_paths.h new file mode 100644 index 000000000..af4a768e8 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/header_paths.h @@ -0,0 +1,46 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef HEADER_PATHS_H +#define HEADER_PATHS_H + +#include <QtCore/QByteArray> +#include <QtCore/QList> + +enum class HeaderType +{ + Standard, + System, // -isystem + Framework, // macOS framework path + FrameworkSystem // macOS framework system path +}; + +class HeaderPath { +public: + QByteArray path; + HeaderType type; + + static QByteArray includeOption(const HeaderPath &p) + { + QByteArray option; + switch (p.type) { + case HeaderType::Standard: + option = QByteArrayLiteral("-I"); + break; + case HeaderType::System: + option = QByteArrayLiteral("-isystem"); + break; + case HeaderType::Framework: + option = QByteArrayLiteral("-F"); + break; + case HeaderType::FrameworkSystem: + option = QByteArrayLiteral("-iframework"); + break; + } + return option + p.path; + } +}; + +using HeaderPaths = QList<HeaderPath>; + +#endif // HEADER_PATHS_H diff --git a/sources/shiboken6/ApiExtractor/icecc.cmake b/sources/shiboken6/ApiExtractor/icecc.cmake new file mode 100644 index 000000000..fa8d3b7cf --- /dev/null +++ b/sources/shiboken6/ApiExtractor/icecc.cmake @@ -0,0 +1,14 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +include (CMakeForceCompiler) +option(ENABLE_ICECC "Enable icecc checking, for distributed compilation") +if (ENABLE_ICECC) + find_program(ICECC icecc) + if (ICECC) + message(STATUS "icecc found! Distributed compilation for all!! huhuhu.") + cmake_force_cxx_compiler(${ICECC} icecc) + else(ICECC) + message(FATAL_ERROR "icecc NOT found! re-run cmake without -DENABLE_ICECC") + endif(ICECC) +endif(ENABLE_ICECC) diff --git a/sources/shiboken6/ApiExtractor/include.cpp b/sources/shiboken6/ApiExtractor/include.cpp new file mode 100644 index 000000000..aee6b7337 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/include.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "include.h" +#include "textstream.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QHash> +#include <QtCore/QTextStream> + +#include "qtcompat.h" + +#include <algorithm> + +using namespace Qt::StringLiterals; + +QString Include::toString() const +{ + if (m_type == IncludePath) + return u"#include <"_s + m_name + u'>'; + if (m_type == LocalPath) + return u"#include \""_s + m_name + u'"'; + return u"import "_s + m_name + u';'; +} + +Qt::strong_ordering compareThreeWay(const Include &lhs, const Include &rhs) noexcept +{ + if (lhs.m_type < rhs.m_type) + return Qt::strong_ordering::less; + if (lhs.m_type > rhs.m_type) + return Qt::strong_ordering::greater; + if (auto c = lhs.m_name.compare(rhs.m_name)) + return c < 0 ? Qt::strong_ordering::less : Qt::strong_ordering::greater; + return Qt::strong_ordering::equal; +} + +QTextStream& operator<<(QTextStream& out, const Include& g) +{ + if (g.isValid()) + out << g.toString() << Qt::endl; + return out; +} + +TextStream& operator<<(TextStream& out, const Include& include) +{ + if (include.isValid()) + out << include.toString() << '\n'; + return out; +} + +TextStream& operator<<(TextStream &out, const IncludeGroup& g) +{ + if (!g.includes.isEmpty()) { + if (!g.title.isEmpty()) + out << "\n// " << g.title << "\n"; + auto includes = g.includes; + std::sort(includes.begin(), includes.end()); + for (const Include &inc : std::as_const(includes)) + out << inc.toString() << '\n'; + } + return out; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const Include &i) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "Include("; + if (i.isValid()) + d << "type=" << i.type() << ", file=\"" << QDir::toNativeSeparators(i.name()) << '"'; + else + d << "invalid"; + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/include.h b/sources/shiboken6/ApiExtractor/include.h new file mode 100644 index 000000000..875a941f9 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/include.h @@ -0,0 +1,95 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef INCLUDE_H +#define INCLUDE_H + +#include <QtCore/QtCompare> +#include <QtCore/QHashFunctions> +#include <QtCore/QString> +#include <QtCore/QList> + +QT_BEGIN_NAMESPACE +class QTextStream; +QT_END_NAMESPACE + +class TextStream; + +class Include +{ +public: + enum IncludeType { + IncludePath, + LocalPath, + TargetLangImport + }; + + Include() = default; + Include(IncludeType t, const QString &nam) : m_type(t), m_name(nam) {}; + + bool isValid() const + { + return !m_name.isEmpty(); + } + + IncludeType type() const + { + return m_type; + } + + QString name() const + { + return m_name; + } + + QString toString() const; + + int compare(const Include &rhs) const; + +private: + friend size_t qHash(Include &inc, size_t seed = 0) noexcept + { + return qHashMulti(seed, inc.m_type, inc.m_name); + } + friend bool comparesEqual(const Include &lhs, const Include &rhs) noexcept + { + return lhs.m_type == rhs.m_type && lhs.m_name == rhs.m_name; + } + friend Qt::strong_ordering compareThreeWay(const Include &lhs, + const Include &rhs) noexcept; + Q_DECLARE_STRONGLY_ORDERED(Include) + + IncludeType m_type = IncludePath; + QString m_name; +}; + +QTextStream& operator<<(QTextStream& out, const Include& include); +TextStream& operator<<(TextStream& out, const Include& include); +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const Include &i); +#endif + +using IncludeList = QList<Include>; + +struct IncludeGroup +{ + QString title; + IncludeList includes; + + void append(const Include &include) + { + IncludeGroup::appendInclude(include, &includes); + } + + static void appendInclude(const Include &include, IncludeList *list) + { + if (include.isValid() && !list->contains(include)) + list->append(include); + } +}; + +TextStream& operator<<(TextStream &out, const IncludeGroup& include); + +using IncludeGroupList = QList<IncludeGroup>; + +#endif diff --git a/sources/shiboken6/ApiExtractor/merge.xsl b/sources/shiboken6/ApiExtractor/merge.xsl new file mode 100644 index 000000000..c6cab5a42 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/merge.xsl @@ -0,0 +1,82 @@ +<?xml version="1.0"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version="1.0"> + <xsl:output method="xml" indent="yes"/> + <xsl:param name="lang" /> + <xsl:param name="source" /> + + <xsl:template match="processing-instruction()" /> + + <xsl:template match="/typesystem"> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:copy> + <xsl:value-of select="." /> + </xsl:copy> + </xsl:for-each> + + <xsl:for-each select="document($source)/typesystem/@*"> + <xsl:copy> + <xsl:value-of select="." /> + </xsl:copy> + </xsl:for-each> + + <xsl:variable name="other" select="document($source)/typesystem/*[not(self::object-type | self::value-type | self::interface-type | self::namespace-type)]" /> + <xsl:if test="$other"> + <xsl:choose> + <xsl:when test="$lang != ''"> + <xsl:element name="language"> + <xsl:attribute name="name" ><xsl:value-of select="$lang" /></xsl:attribute> + <xsl:copy-of select="$other" /> + </xsl:element> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$other" /> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <xsl:apply-templates select="node()" /> + + </xsl:copy> + </xsl:template> + + + + <xsl:template match="/typesystem/*[self::object-type | self::value-type | self::interface-type | self::namespace-type]"> + <xsl:variable name="name" select="name()" /> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:copy> + <xsl:value-of select="." /> + </xsl:copy> + </xsl:for-each> + + <xsl:apply-templates select="node()" /> + + <xsl:variable name="other" select="document($source)/typesystem/*[name() = $name][@name = current()/@name]" /> + <xsl:if test="$other"> + <xsl:choose> + <xsl:when test="$lang != ''"> + <xsl:element name="language"> + <xsl:attribute name="name" ><xsl:value-of select="$lang" /></xsl:attribute> + <xsl:copy-of select="$other/node()" /> + </xsl:element> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$other/node()" /> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:copy> + </xsl:template> + + <!-- Plain identity transform. --> + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*"/> + <xsl:apply-templates select="node()"/> + </xsl:copy> + </xsl:template> + +</xsl:stylesheet> diff --git a/sources/shiboken6/ApiExtractor/messages.cpp b/sources/shiboken6/ApiExtractor/messages.cpp new file mode 100644 index 000000000..170595660 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/messages.cpp @@ -0,0 +1,1004 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "messages.h" +#include "abstractmetaenum.h" +#include "abstractmetafield.h" +#include "abstractmetafunction.h" +#include "abstractmetalang.h" +#include "modifications.h" +#include "sourcelocation.h" +#include "typedatabase.h" +#include "functiontypeentry.h" +#include "enumtypeentry.h" +#include "smartpointertypeentry.h" +#include <codemodel.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QStringList> +#include <QtCore/QXmlStreamReader> + +using namespace Qt::StringLiterals; + +// abstractmetabuilder.cpp + +static QTextStream &operator<<(QTextStream &s, Access a) +{ + switch (a) { + case Access::Public: + s << "public"; + break; + case Access::Protected: + s << "protected"; + break; + case Access::Private: + s << "private"; + break; + } + return s; +} + +QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass, + const QString &signature, + const QString &originalSignature, + const QStringList &possibleSignatures, + const AbstractMetaFunctionCList &allFunctions) +{ + QString result; + QTextStream str(&result); + str << klass->typeEntry()->sourceLocation() << "signature '" + << signature << '\''; + if (!originalSignature.isEmpty() && originalSignature != signature) + str << " (specified as '" << originalSignature << "')"; + str << " for function modification in '" + << klass->qualifiedCppName() << "' not found."; + if (!possibleSignatures.isEmpty()) { + str << "\n Possible candidates:\n"; + for (const auto &s : possibleSignatures) + str << " " << s << '\n'; + } else if (!allFunctions.isEmpty()) { + str << "\n No candidates were found. Member functions:\n"; + const auto maxCount = qMin(qsizetype(10), allFunctions.size()); + for (qsizetype f = 0; f < maxCount; ++f) + str << " " << allFunctions.at(f)->minimalSignature() << '\n'; + if (maxCount < allFunctions.size()) + str << " ...\n"; + } + return result; +} + +QString msgArgumentIndexOutOfRange(const AbstractMetaFunction *func, int index) +{ + QString result; + QTextStream str(&result); + str <<"Index " << index << " out of range for " << func->classQualifiedSignature() << '.'; + return result; +} + +QString msgTypeModificationFailed(const QString &type, int n, + const AbstractMetaFunction *func, + const QString &why) +{ + QString result; + QTextStream str(&result); + str << "Unable to modify the "; + if (n == 0) + str << "return type"; + else + str << "type of argument " << n; + + str << " of "; + if (auto c = func->ownerClass()) + str << c->name() << "::"; + str << func->signature() << " to \"" << type << "\": " << why; + return result; +} + +QString msgInvalidArgumentModification(const AbstractMetaFunctionCPtr &func, + int argIndex) +{ + QString result; + QTextStream str(&result); + str << "Invalid "; + if (argIndex == 0) + str << "return type modification"; + else + str << "modification of argument " << argIndex; + str << " for " << func->classQualifiedSignature(); + return result; +} + +QString msgArgumentOutOfRange(int number, int minValue, int maxValue) +{ + QString result; + QTextStream(&result) << "Argument number " << number + << " out of range " << minValue << ".." << maxValue << '.'; + return result; +} + +QString msgArgumentRemovalFailed(const AbstractMetaFunction *func, int n, + const QString &why) +{ + QString result; + QTextStream str(&result); + str << "Unable to remove argument " << n << " of "; + if (auto c = func->ownerClass()) + str << c->name() << "::"; + str << func->signature() << ": " << why; + return result; +} + +template <class Stream> +static void msgFormatEnumType(Stream &str, + const EnumModelItem &enumItem, + const QString &className) +{ + switch (enumItem->enumKind()) { + case CEnum: + str << "Enum '" << enumItem->qualifiedName().join(u"::"_s) << '\''; + break; + case AnonymousEnum: { + const EnumeratorList &values = enumItem->enumerators(); + str << "Anonymous enum ("; + switch (values.size()) { + case 0: + break; + case 1: + str << values.constFirst()->name(); + break; + case 2: + str << values.at(0)->name() << ", " << values.at(1)->name(); + break; + default: + str << values.at(0)->name() << ", ... , " + << values.at(values.size() - 1)->name(); + break; + } + str << ')'; + } + break; + case EnumClass: + str << "Scoped enum '" << enumItem->qualifiedName().join(u"::"_s) << '\''; + break; + } + if (!className.isEmpty()) + str << " (class: " << className << ')'; +} + +static void formatAddedFuncError(const QString &addedFuncName, + const AbstractMetaClassCPtr &context, + QTextStream &str) +{ + if (context) { + str << context->typeEntry()->sourceLocation() + << "Unable to traverse function \"" << addedFuncName + << "\" added to \"" << context->name() << "\": "; + } else { + str << "Unable to traverse added global function \"" + << addedFuncName << "\": "; + } +} + +QString msgAddedFunctionInvalidArgType(const QString &addedFuncName, + const QStringList &typeName, + int pos, const QString &why, + const AbstractMetaClassCPtr &context) +{ + QString result; + QTextStream str(&result); + formatAddedFuncError(addedFuncName, context, str); + str << "Unable to translate type \"" << typeName.join(u"::"_s) + << "\" of argument " << pos << " of added function \"" + << addedFuncName << "\": " << why; + return result; +} + +QString msgAddedFunctionInvalidReturnType(const QString &addedFuncName, + const QStringList &typeName, const QString &why, + const AbstractMetaClassCPtr &context) +{ + QString result; + QTextStream str(&result); + formatAddedFuncError(addedFuncName, context, str); + str << "Unable to translate return type \"" << typeName.join(u"::"_s) + << "\" of added function \"" << addedFuncName << "\": " + << why; + return result; +} + +QString msgUnnamedArgumentDefaultExpression(const AbstractMetaClassCPtr &context, + int n, const QString &className, + const AbstractMetaFunction *f) +{ + QString result; + QTextStream str(&result); + if (context) + str << context->sourceLocation(); + str << "Argument " << n << " on function '" << className << "::" + << f->minimalSignature() << "' has default expression but does not have name."; + return result; +} + +QString msgClassOfEnumNotFound(const EnumTypeEntryCPtr &entry) +{ + QString result; + QTextStream str(&result); + str << entry->sourceLocation() << "AbstractMeta::findEnum(), unknown class '" + << entry->parent()->qualifiedCppName() << "' in '" + << entry->qualifiedCppName() << '\''; + return result; +} + +QString msgNoEnumTypeEntry(const EnumModelItem &enumItem, + const QString &className) +{ + QString result; + QTextStream str(&result); + str << enumItem->sourceLocation(); + msgFormatEnumType(str, enumItem, className); + str << " does not have a type entry (type systems: " + << TypeDatabase::instance()->loadedTypeSystemNames() << ')'; + return result; +} + +QString msgNoEnumTypeConflict(const EnumModelItem &enumItem, + const QString &className, + const TypeEntryCPtr &t) +{ + QString result; + QDebug debug(&result); // Use the debug operator for TypeEntry::Type + debug.noquote(); + debug.nospace(); + debug << enumItem->sourceLocation().toString(); + msgFormatEnumType(debug, enumItem, className); + debug << " is not an enum (type: " << t->type() << ')'; + return result; +} + +QString msgNamespaceNoTypeEntry(const NamespaceModelItem &item, + const QString &fullName) +{ + QString result; + QTextStream str(&result); + str << item->sourceLocation() << "namespace '" << fullName + << "' does not have a type entry (type systems: " + << TypeDatabase::instance()->loadedTypeSystemNames() << ')'; + return result; +} + +QString msgNamespaceNotFound(const QString &name) +{ + return u"namespace '"_s + name + u"' not found."_s; +} + +QString msgAmbiguousVaryingTypesFound(const QString &qualifiedName, const TypeEntryCList &te) +{ + QString result = u"Ambiguous types of varying types found for \""_s + qualifiedName + + u"\": "_s; + QDebug(&result) << te; + return result; +} + +QString msgAmbiguousTypesFound(const QString &qualifiedName, const TypeEntryCList &te) +{ + QString result = u"Ambiguous types found for \""_s + qualifiedName + + u"\": "_s; + QDebug(&result) << te; + return result; +} + +QString msgUnmatchedParameterType(const ArgumentModelItem &arg, int n, + const QString &why) +{ + QString result; + QTextStream str(&result); + str << "unmatched type '" << arg->type().toString() << "' in parameter #" + << (n + 1); + if (!arg->name().isEmpty()) + str << " \"" << arg->name() << '"'; + str << ": " << why; + return result; +} + +QString msgUnmatchedReturnType(const FunctionModelItem &functionItem, + const QString &why) +{ + return u"unmatched return type '"_s + + functionItem->type().toString() + + u"': "_s + why; +} + +QString msgSkippingFunction(const FunctionModelItem &functionItem, + const QString &signature, const QString &why) +{ + QString result; + QTextStream str(&result); + str << functionItem->sourceLocation() << "skipping " + << functionItem->accessPolicy() << ' '; + const bool isAbstract = functionItem->attributes().testFlag(FunctionAttribute::Abstract); + if (isAbstract) + str << "abstract "; + str << "function '" << signature << "', " << why; + if (isAbstract) { + str << "\nThis will lead to compilation errors due to not " + "being able to instantiate the wrapper."; + } + return result; +} + +QString msgShadowingFunction(const AbstractMetaFunction *f1, + const AbstractMetaFunction *f2) +{ + auto f2Class = f2->implementingClass(); + QString result; + QTextStream str(&result); + str << f2Class->sourceLocation() << "Shadowing: " << f1->classQualifiedSignature() + << " and " << f2->classQualifiedSignature(); + return result; +} + +QString msgSignalOverloaded(const AbstractMetaClassCPtr &c, + const AbstractMetaFunction *f) +{ + QString result; + QTextStream str(&result); + str << c->sourceLocation() << "signal '" << f->name() << "' in class '" + << c->name() << "' is overloaded."; + return result; +} + +QString msgSkippingField(const VariableModelItem &field, const QString &className, + const QString &type) +{ + QString result; + QTextStream str(&result); + str << field->sourceLocation() << "skipping " << field->accessPolicy() + << " field '" << className << "::" << field->name() + << "' with unmatched type '" << type << '\''; + return result; +} + +static const char msgCompilationError[] = + "This could potentially lead to compilation errors."; + +QString msgTypeNotDefined(const TypeEntryCPtr &entry) +{ + QString result; + QTextStream str(&result); + const bool hasConfigCondition = entry->isComplex() + && std::static_pointer_cast<const ConfigurableTypeEntry>(entry)->hasConfigCondition(); + str << entry->sourceLocation() << "type '" <<entry->qualifiedCppName() + << "' is specified in typesystem, but not defined"; + if (hasConfigCondition) + str << " (disabled by configuration?)."; + else + str << ". " << msgCompilationError; + return result; +} + +QString msgGlobalFunctionNotDefined(const FunctionTypeEntryCPtr &fte, + const QString &signature, + const QStringList &candidates) +{ + QString result; + QTextStream str(&result); + str << fte->sourceLocation() << "Global function '" << signature + << "' is specified in typesystem, but not defined."; + if (!candidates.isEmpty()) + str << " Candidates are: " << candidates.join(u", "_s); + str << ' ' << msgCompilationError; + return result; +} + +QString msgStrippingArgument(const FunctionModelItem &f, int i, + const QString &originalSignature, + const ArgumentModelItem &arg, + const QString &reason) +{ + QString result; + QTextStream str(&result); + str << f->sourceLocation() << "Stripping argument #" << (i + 1) << " of " + << originalSignature << " due to unmatched type \"" + << arg->type().toString() << "\" with default expression \"" + << arg->defaultValueExpression() << "\": " << reason; + return result; +} + +QString msgEnumNotDefined(const EnumTypeEntryCPtr &t) +{ + QString result; + QTextStream str(&result); + str << t->sourceLocation() << "enum '" << t->qualifiedCppName() + << "' is specified in typesystem, but not declared."; + return result; +} + +QString msgUnknownBase(const AbstractMetaClassCPtr &metaClass, + const QString &baseClassName) +{ + QString result; + QTextStream str(&result); + str << metaClass->sourceLocation() << "Base class '" << baseClassName << "' of class '" + << metaClass->name() << "' not found in the code for setting up inheritance."; + return result; +} + +QString msgBaseNotInTypeSystem(const AbstractMetaClassCPtr &metaClass, + const QString &baseClassName) +{ + QString result; + QTextStream str(&result); + str << metaClass->sourceLocation() << "Base class '" << baseClassName << "' of class '" + << metaClass->name() << "' not found in the type system for setting up inheritance."; + return result; +} + +QString msgArrayModificationFailed(const FunctionModelItem &functionItem, + const QString &className, + const QString &errorMessage) +{ + QString result; + QTextStream str(&result); + str << functionItem->sourceLocation() << "While traversing " << className + << ": " << errorMessage; + return result; +} + +QString msgCannotResolveEntity(const QString &name, const QString &reason) +{ + return u"Cannot resolve entity \""_s + name + u"\": "_s + reason; +} + +QString msgCannotSetArrayUsage(const QString &function, int i, const QString &reason) +{ + return function + u": Cannot use parameter "_s + + QString::number(i + 1) + u" as an array: "_s + reason; +} + +QString msgUnableToTranslateType(const QString &t, const QString &why) +{ + return u"Unable to translate type \""_s + t + u"\": "_s + why; +} + +QString msgUnableToTranslateType(const TypeInfo &typeInfo, + const QString &why) +{ + return msgUnableToTranslateType(typeInfo.toString(), why); +} + +QString msgCannotFindTypeEntry(const QString &t) +{ + return u"Cannot find type entry for \""_s + t + u"\"."_s; +} + +QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &smartPointerType) +{ + return u"Cannot find type entry \""_s + t + + u"\" for instantiation of \""_s +smartPointerType + u"\"."_s; +} + +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, + const QString &what) +{ + return "While inheriting template "_L1 + subclass->name() + + " from "_L1 + info.toString() + ": "_L1 + what; +} + +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why) +{ + return "Ignoring template parameter "_L1 + typeName + + ": "_L1 + QLatin1StringView(why); +} + +QString msgInvalidSmartPointerType(const TypeInfo &i) +{ + return u"Invalid smart pointer type \""_s +i.toString() + u"\"."_s; +} + +QString msgCannotFindSmartPointerInstantion(const TypeInfo &i) +{ + return u"Cannot find instantiation of smart pointer type for \""_s + + i.toString() + u"\"."_s; +} + +QString msgCannotTranslateTemplateArgument(int i, + const TypeInfo &typeInfo, + const QString &why) +{ + QString result; + QTextStream(&result) << "Unable to translate template argument " + << (i + 1) << typeInfo.toString() << ": " << why; + return result; +} + +QString msgDisallowThread(const AbstractMetaFunction *f) +{ + QString result; + QTextStream str(&result); + str <<"Disallowing threads for "; + if (auto c = f->declaringClass()) + str << c->name() << "::"; + str << f->name() << "()."; + return result; +} + +QString msgNamespaceToBeExtendedNotFound(const QString &namespaceName, const QString &packageName) +{ + return u"The namespace '"_s + namespaceName + + u"' to be extended cannot be found in package "_s + + packageName + u'.'; +} + +QString msgPropertyTypeParsingFailed(const QString &name, const QString &typeName, + const QString &why) +{ + QString result; + QTextStream str(&result); + str << "Unable to decide type of property: \"" << name << "\" (" << typeName + << "): " << why; + return result; +} + +QString msgPropertyExists(const QString &className, const QString &name) +{ + return u"class "_s + className + u" already has a property \""_s + + name + u"\" (defined by Q_PROPERTY)."_s; +} + +QString msgFunctionVisibilityModified(const AbstractMetaClassCPtr &c, + const AbstractMetaFunction *f) +{ + QString result; + QTextStream str(&result); + str << c->sourceLocation() << "Visibility of function '" << f->name() + << "' modified in class '"<< c->name() << '\''; + return result; +} + +QString msgUsingMemberClassNotFound(const AbstractMetaClassCPtr &c, + const QString &baseClassName, + const QString &memberName) +{ + QString result; + QTextStream str(&result); + str << c->sourceLocation() << "base class \"" << baseClassName + << "\" of \"" << c->qualifiedCppName() << "\" for using member \"" + << memberName << "\" not found."; + return result; +} +// docparser.cpp + +QString msgCannotFindDocumentation(const QString &fileName, + const char *what, const QString &name, + const QString &query) +{ + QString result; + QTextStream str(&result); + str << "Cannot find documentation for " << what + << ' ' << name << " in:\n " << QDir::toNativeSeparators(fileName); + if (!query.isEmpty()) + str << "\n using query:\n " << query; + return result; +} + +QString msgFallbackForDocumentation(const QString &fileName, + const char *what, const QString &name, + const QString &query) +{ + QString result; + QTextStream str(&result); + str << "Fallback used while trying to find documentation for " << what + << ' ' << name << " in:\n " << QDir::toNativeSeparators(fileName); + if (!query.isEmpty()) + str << "\n using query:\n " << query; + return result; +} + +static QString functionDescription(const AbstractMetaFunction *function) +{ + QString result = u'"' + function->classQualifiedSignature() + u'"'; + if (function->flags().testFlag(AbstractMetaFunction::Flag::HiddenFriend)) + result += u" (hidden friend)"_s; + if (function->flags().testFlag(AbstractMetaFunction::Flag::InheritedFromTemplate)) + result += u" (inherited from template)"_s; + return result; +} + +QString msgCannotFindDocumentation(const QString &fileName, + const AbstractMetaFunction *function, + const QString &query) +{ + return msgCannotFindDocumentation(fileName, "function", + functionDescription(function), query); +} + +QString msgFallbackForDocumentation(const QString &fileName, + const AbstractMetaFunction *function, + const QString &query) +{ + return msgFallbackForDocumentation(fileName, "function", + functionDescription(function), query); +} + +QString msgCannotFindDocumentation(const QString &fileName, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaEnum &e, + const QString &query) +{ + QString name = e.name(); + if (metaClass != nullptr) + name.prepend(metaClass->name() + "::"_L1); + return msgCannotFindDocumentation(fileName, "enum", name, query); +} + +QString msgCannotFindDocumentation(const QString &fileName, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaField &f, + const QString &query) +{ + QString name = f.name(); + if (metaClass != nullptr) + name.prepend(metaClass->name() + "::"_L1); + return msgCannotFindDocumentation(fileName, "field", name, query); +} + +QString msgXpathDocModificationError(const DocModificationList& mods, + const QString &what) +{ + QString result; + QTextStream str(&result); + str << "Error when applying modifications ("; + for (const DocModification &mod : mods) { + if (mod.mode() == TypeSystem::DocModificationXPathReplace) { + str << '"' << mod.xpath() << "\" -> \""; + const QString simplified = mod.code().simplified(); + if (simplified.size() > 20) + str << QStringView{simplified}.left(20) << "..."; + else + str << simplified; + str << '"'; + } + } + str << "): " << what; + return result; +} + +// fileout.cpp + +QString msgCannotOpenForReading(const QFile &f) +{ + return QString::fromLatin1("Failed to open file '%1' for reading: %2") + .arg(QDir::toNativeSeparators(f.fileName()), f.errorString()); +} + +QString msgCannotOpenForWriting(const QFile &f) +{ + return QString::fromLatin1("Failed to open file '%1' for writing: %2") + .arg(QDir::toNativeSeparators(f.fileName()), f.errorString()); +} + +QString msgWriteFailed(const QFile &f, qsizetype size) +{ + QString result; + QTextStream(&result) << "Failed to write " << size << "bytes to '" + << QDir::toNativeSeparators(f.fileName()) << "': " + << f.errorString(); + return result; +} + +// generator.cpp + +QString msgCannotUseEnumAsInt(const QString &name) +{ + return u"Cannot convert the protected scoped enum \""_s + name + + u"\" to type int when generating wrappers for the protected hack. " + "Compilation errors may occur when used as a function argument."_s; +} + +QString msgCannotFindSmartPointerGetter(const SmartPointerTypeEntryCPtr &te) +{ + return u"Getter \""_s + te->getter() + u"()\" of smart pointer \""_s + + te->name() + u"\" not found."_s; +} + +QString msgCannotFindSmartPointerMethod(const SmartPointerTypeEntryCPtr &te, const QString &m) +{ + return u"Method \""_s + m + u"()\" of smart pointer \""_s + + te->name() + u"\" not found."_s; +} + +QString msgMethodNotFound(const AbstractMetaClassCPtr &klass, const QString &name) +{ + return u"Method \""_s + name + u"\" not found in class "_s + + klass->name() + u'.'; +} + +// main.cpp + +QString msgLeftOverArguments(const QString &remainingArgs, const QStringList &argV) +{ + QString message; + QTextStream str(&message); + str << "shiboken: Unprocessed arguments: " << remainingArgs + << "\nCommand line: " << argV.join(u' '); + return message; +} + +QString msgInvalidVersion(const QString &package, const QString &version) +{ + return u"Invalid version \""_s + version + + u"\" specified for package "_s + package + u'.'; +} + +QString msgCyclicDependency(const QString &funcName, const QString &graphName, + const AbstractMetaFunctionCList &cyclic, + const AbstractMetaFunctionCList &involvedConversions) +{ + QString result; + QTextStream str(&result); + str << "Cyclic dependency found on overloaddata for \"" << funcName + << "\" method! The graph boy saved the graph at \"" << QDir::toNativeSeparators(graphName) + << "\". Cyclic functions:"; + for (const auto &c : cyclic) + str << ' ' << c->signature(); + if (const auto count = involvedConversions.size()) { + str << " Implicit conversions (" << count << "): "; + for (qsizetype i = 0; i < count; ++i) { + if (i) + str << ", \""; + str << involvedConversions.at(i)->signature() << '"'; + if (const auto c = involvedConversions.at(i)->implementingClass()) + str << '(' << c->name() << ')'; + } + } + return result; +} + +// shibokengenerator.cpp + +QString msgClassNotFound(const TypeEntryCPtr &t) +{ + return u"Could not find class \""_s + + t->qualifiedCppName() + + u"\" in the code model. Maybe it is forward declared?"_s; +} + +QString msgEnclosingClassNotFound(const TypeEntryCPtr &t) +{ + QString result; + QTextStream str(&result); + str << "Warning: Enclosing class \"" << t->parent()->name() + << "\" of class \"" << t->name() << "\" not found."; + return result; +} + +QString msgUnknownOperator(const AbstractMetaFunction *func) +{ + QString result = u"Unknown operator: \""_s + func->originalName() + + u'"'; + if (const auto c = func->implementingClass()) + result += u" in class: "_s + c->name(); + return result; +} + +QString msgWrongIndex(const char *varName, const QString &capture, + const AbstractMetaFunction *func) +{ + QString result; + QTextStream str(&result); + str << "Wrong index for " << varName << " variable (" << capture << ") on "; + if (const auto c = func->implementingClass()) + str << c->name() << "::"; + str << func->signature(); + return result; +} + +QString msgCannotFindType(const QString &type, const QString &variable, + const QString &why) +{ + QString result; + QTextStream(&result) << "Could not find type '" + << type << "' for use in '" << variable << "' conversion: " << why + << "\nMake sure to use the full C++ name, e.g. 'Namespace::Class'."; + return result; +} + +QString msgCannotBuildMetaType(const QString &s) +{ + return u"Unable to build meta type for \""_s + s + u"\": "_s; +} + +QString msgCouldNotFindMinimalConstructor(const QString &where, const QString &type, const QString &why) +{ + QString result; + QTextStream str(&result); + str << where << ": Could not find a minimal constructor for type '" << type << '\''; + if (why.isEmpty()) + str << '.'; + else + str << ": " << why << ' '; + str << "This will result in a compilation error."; + return result; +} + +// typedatabase.cpp + +QString msgRejectReason(const TypeRejection &r, const QString &needle) +{ + QString result; + QTextStream str(&result); + switch (r.matchType) { + case TypeRejection::ExcludeClass: + str << "matches class exclusion \"" << r.className.pattern() << '"'; + break; + case TypeRejection::Function: + case TypeRejection::Field: + case TypeRejection::Enum: + str << "matches class \"" << r.className.pattern() << "\" and \"" + << r.pattern.pattern() << '"'; + break; + case TypeRejection::ArgumentType: + case TypeRejection::ReturnType: + str << "matches class \"" << r.className.pattern() << "\" and \"" + << needle << "\" matches \"" << r.pattern.pattern() << '"'; + break; + } + return result; +} + +// typesystem.cpp + +QString msgCannotFindNamespaceToExtend(const QString &name, + const QString &extendsPackage) +{ + return u"Cannot find namespace "_s + name + + u" in package "_s + extendsPackage; +} + +QString msgExtendingNamespaceRequiresPattern(const QString &name) +{ + return u"Namespace "_s + name + + u" requires a file pattern since it extends another namespace."_s; +} + +QString msgInvalidRegularExpression(const QString &pattern, const QString &why) +{ + return u"Invalid pattern \""_s + pattern + u"\": "_s + why; +} + +QString msgNoRootTypeSystemEntry() +{ + return u"Type system entry appears out of order, there does not seem to be a root type system element."_s; +} + +QString msgIncorrectlyNestedName(const QString &name) +{ + return u"Nesting types by specifying '::' is no longer supported ("_s + + name + u")."_s; +} + +QString msgCannotFindView(const QString &viewedName, const QString &name) +{ + return u"Unable to find viewed type "_s + viewedName + + u" for "_s + name; +} + +QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel) +{ + QString result; + QTextStream str(&result); + str << "Cannot find snippet \"" << snippetLabel << "\" in " + << QDir::toNativeSeparators(file) << '.'; + return result; +} + +QString msgSnippetError(const QString &context, const char *what) +{ + return "Error processing code snippet of "_L1 + context + + ": "_L1 + QString::fromUtf8(what); +} + +QString msgUnableToResolveTypedef(const QString &sourceType, const QString &sourceName) +{ + QString result; + QTextStream(&result) << "Unable to resolve typedef \"" << sourceType + << "\": Could not find a value, container, object or smart pointer type named \"" + << sourceName << "\"."; + return result; +} + +// cppgenerator.cpp + +QString msgPureVirtualFunctionRemoved(const AbstractMetaFunction *f) +{ + QString result; + auto klass = f->ownerClass(); + QTextStream str(&result); + str << klass->sourceLocation() << "Pure virtual method '" << klass->name() + << "::"<< f->minimalSignature() + << "' must be implemented but was completely removed on type system."; + return result; +} + +QString msgUnknownTypeInArgumentTypeReplacement(const QString &typeReplaced, + const AbstractMetaFunction *f) +{ + QString result; + QTextStream str(&result); + if (auto klass = f->ownerClass()) + str << klass->sourceLocation(); + str << "Unknown type '" << typeReplaced + << "' used as argument type replacement in function '" << f->signature() + << "', the generated code may be broken."; + return result; +} + +QString msgDuplicateBuiltInTypeEntry(const QString &name) +{ + return u"A type entry duplicating the built-in type \""_s + + name + u"\" was found. It is ignored."_s; +} + +QString msgDuplicateTypeEntry(const QString &name) +{ + return u"Duplicate type entry: '"_s + name + u"'."_s; +} + +QString msgInvalidTargetLanguageApiName(const QString &name) +{ + return u"Invalid target language API name \""_s + + name + u"\"."_s; +} + +QString msgUnknownCheckFunction(const TypeEntryCPtr &t) +{ + return u"Unknown check function for type: '"_s + + t->qualifiedCppName() + u"'."_s; +} + +QString msgArgumentClassNotFound(const AbstractMetaFunctionCPtr &func, + const TypeEntryCPtr &t) +{ + QString result; + QTextStream(&result) << "Internal Error: Class \"" << t->qualifiedCppName() + << "\" for \"" << func->classQualifiedSignature() << "\" not found!"; + return result; +} + +QString msgMissingCustomConversion(const TypeEntryCPtr &t) +{ + QString result; + QTextStream(&result) << "Entry \"" << t->qualifiedCppName() + << "\" is missing a custom conversion."; + return result; +} + +QString msgUnknownArrayPointerConversion(const QString &s) +{ + return u"Warning: Falling back to pointer conversion for unknown array type \""_s + + s + u"\""_s; +} + +QString msgMissingProjectFileMarker(const QString &name, const QByteArray &startMarker) +{ + return u"First line of project file \""_s + QDir::toNativeSeparators(name) + + u"\" must be the string \""_s + QString::fromLatin1(startMarker) + u"\"."_s; +} + +QString msgInvalidLanguageLevel(const QString &l) +{ + return u"Invalid argument for language level: \""_s + l + u"\"."_s; +} + +QString msgCannotFindImage(const QString &href, const QString &context, + const QString &candidate) +{ + return "Cannot resolve image "_L1 + href + " for "_L1 + context + + " (tried "_L1 + QDir::toNativeSeparators(candidate) + ")."_L1; +} diff --git a/sources/shiboken6/ApiExtractor/messages.h b/sources/shiboken6/ApiExtractor/messages.h new file mode 100644 index 000000000..5216b26a7 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/messages.h @@ -0,0 +1,267 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MESSAGES_H +#define MESSAGES_H + +#include "abstractmetalang_typedefs.h" +#include "parser/codemodel_fwd.h" +#include "modifications_typedefs.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QString> + +class EnumTypeEntry; +class FunctionTypeEntry; +class SmartPointerTypeEntry; +class TypeEntry; +class TypeInfo; +struct TypeRejection; + +QT_FORWARD_DECLARE_CLASS(QDir) +QT_FORWARD_DECLARE_CLASS(QFile) +QT_FORWARD_DECLARE_CLASS(QXmlStreamReader) + +QString msgAddedFunctionInvalidArgType(const QString &addedFuncName, + const QStringList &typeName, + int pos, const QString &why, + const AbstractMetaClassCPtr &context = {}); + +QString msgAddedFunctionInvalidReturnType(const QString &addedFuncName, + const QStringList &typeName, const QString &why, + const AbstractMetaClassCPtr &context = {}); + +QString msgUnnamedArgumentDefaultExpression(const AbstractMetaClassCPtr &context, + int n, const QString &className, + const AbstractMetaFunction *f); + +QString msgArgumentIndexOutOfRange(const AbstractMetaFunction *func, int index); + +QString msgNoFunctionForModification(const AbstractMetaClassCPtr &klass, + const QString &signature, + const QString &originalSignature, + const QStringList &possibleSignatures, + const AbstractMetaFunctionCList &allFunctions); + +QString msgTypeModificationFailed(const QString &type, int n, + const AbstractMetaFunction *func, + const QString &why); + +QString msgInvalidArgumentModification(const AbstractMetaFunctionCPtr &func, + int argIndex); + +QString msgArgumentOutOfRange(int number, int minValue, int maxValue); + +QString msgArgumentRemovalFailed(const AbstractMetaFunction *func, int n, + const QString &why); + +QString msgClassOfEnumNotFound(const EnumTypeEntryCPtr &entry); + +QString msgNoEnumTypeEntry(const EnumModelItem &enumItem, + const QString &className); + + +QString msgNoEnumTypeConflict(const EnumModelItem &enumItem, + const QString &className, + const TypeEntryCPtr &t); + +QString msgNamespaceNoTypeEntry(const NamespaceModelItem &item, + const QString &fullName); + +QString msgNamespaceNotFound(const QString &name); + +QString msgAmbiguousVaryingTypesFound(const QString &qualifiedName, const TypeEntryCList &te); +QString msgAmbiguousTypesFound(const QString &qualifiedName, const TypeEntryCList &te); + +QString msgUnmatchedParameterType(const ArgumentModelItem &arg, int n, + const QString &why); + +QString msgUnmatchedReturnType(const FunctionModelItem &functionItem, + const QString &why); + +QString msgShadowingFunction(const AbstractMetaFunction *f1, + const AbstractMetaFunction *f2); + +QString msgSignalOverloaded(const AbstractMetaClassCPtr &c, + const AbstractMetaFunction *f); + +QString msgSkippingFunction(const FunctionModelItem &functionItem, + const QString &signature, const QString &why); + +QString msgSkippingField(const VariableModelItem &field, const QString &className, + const QString &type); + +QString msgTypeNotDefined(const TypeEntryCPtr &entry); + +QString msgGlobalFunctionNotDefined(const FunctionTypeEntryCPtr &fte, + const QString &signature, + const QStringList &candidates); + +QString msgStrippingArgument(const FunctionModelItem &f, int i, + const QString &originalSignature, + const ArgumentModelItem &arg, + const QString &reason); + +QString msgEnumNotDefined(const EnumTypeEntryCPtr &t); + +QString msgUnknownBase(const AbstractMetaClassCPtr &metaClass, + const QString &baseClassName); + +QString msgBaseNotInTypeSystem(const AbstractMetaClassCPtr &metaClass, + const QString &baseClassName); + +QString msgArrayModificationFailed(const FunctionModelItem &functionItem, + const QString &className, + const QString &errorMessage); + +QString msgCannotResolveEntity(const QString &name, const QString &reason); + +QString msgCannotSetArrayUsage(const QString &function, int i, const QString &reason); + +QString msgUnableToTranslateType(const QString &t, const QString &why); + +QString msgUnableToTranslateType(const TypeInfo &typeInfo, + const QString &why); + +QString msgCannotFindTypeEntry(const QString &t); + +QString msgCannotFindTypeEntryForSmartPointer(const QString &t, const QString &smartPointerType); +QString msgInheritTemplateIssue(const AbstractMetaClassPtr &subclass, + const TypeInfo &info, const QString &what); +QString msgIgnoringTemplateParameter(const QString &typeName, + const char *why); +QString msgInvalidSmartPointerType(const TypeInfo &i); +QString msgCannotFindSmartPointerInstantion(const TypeInfo &i); + +QString msgCannotTranslateTemplateArgument(int i, + const TypeInfo &typeInfo, + const QString &why); + +QString msgDisallowThread(const AbstractMetaFunction *f); + +QString msgNamespaceToBeExtendedNotFound(const QString &namespaceName, const QString &packageName); + +QString msgPropertyTypeParsingFailed(const QString &name, const QString &typeName, + const QString &why); +QString msgPropertyExists(const QString &className, const QString &name); + +QString msgFunctionVisibilityModified(const AbstractMetaClassCPtr &c, + const AbstractMetaFunction *f); + +QString msgUsingMemberClassNotFound(const AbstractMetaClassCPtr &c, + const QString &baseClassName, + const QString &memberName); + +QString msgCannotFindDocumentation(const QString &fileName, + const char *what, const QString &name, + const QString &query = {}); + +QString msgFallbackForDocumentation(const QString &fileName, + const char *what, const QString &name, + const QString &query = {}); + +QString msgCannotFindDocumentation(const QString &fileName, + const AbstractMetaFunction *function, + const QString &query = {}); + +QString msgFallbackForDocumentation(const QString &fileName, + const AbstractMetaFunction *function, + const QString &query = {}); + +QString msgCannotFindDocumentation(const QString &fileName, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaEnum &e, + const QString &query = {}); + +QString msgCannotFindDocumentation(const QString &fileName, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaField &f, + const QString &query); + +QString msgXpathDocModificationError(const DocModificationList& mods, + const QString &what); + +QString msgCannotOpenForReading(const QFile &f); + +QString msgCannotOpenForWriting(const QFile &f); + +QString msgWriteFailed(const QFile &f, qsizetype size); + +QString msgCannotUseEnumAsInt(const QString &name); + +QString msgCannotFindSmartPointerGetter(const SmartPointerTypeEntryCPtr &); + +QString msgCannotFindSmartPointerMethod(const SmartPointerTypeEntryCPtr &te, const QString &m); + +QString msgMethodNotFound(const AbstractMetaClassCPtr &klass, const QString &name); + +QString msgLeftOverArguments(const QString &remainingArgs, const QStringList &argV); + +QString msgInvalidVersion(const QString &package, const QString &version); + +QString msgCannotFindNamespaceToExtend(const QString &name, + const QString &extendsPackage); + +QString msgExtendingNamespaceRequiresPattern(const QString &name); + +QString msgInvalidRegularExpression(const QString &pattern, const QString &why); + +QString msgNoRootTypeSystemEntry(); + +QString msgIncorrectlyNestedName(const QString &name); + +QString msgCannotFindView(const QString &viewedName, const QString &name); + +QString msgCannotFindSnippet(const QString &file, const QString &snippetLabel); +QString msgSnippetError(const QString &context, const char *what); +QString msgUnableToResolveTypedef(const QString &sourceType, const QString &sourceName); + +QString msgCyclicDependency(const QString &funcName, const QString &graphName, + const AbstractMetaFunctionCList &cyclic, + const AbstractMetaFunctionCList &involvedConversions); + +QString msgClassNotFound(const TypeEntryCPtr &t); + +QString msgEnclosingClassNotFound(const TypeEntryCPtr &t); + +QString msgUnknownOperator(const AbstractMetaFunction *func); + +QString msgWrongIndex(const char *varName, const QString &capture, + const AbstractMetaFunction *func); + +QString msgCannotFindType(const QString &type, const QString &variable, + const QString &why); + +QString msgCannotBuildMetaType(const QString &s); + +QString msgCouldNotFindMinimalConstructor(const QString &where, const QString &type, + const QString &why = QString()); + +QString msgRejectReason(const TypeRejection &r, const QString &needle = QString()); + +QString msgPureVirtualFunctionRemoved(const AbstractMetaFunction *f); + +QString msgUnknownTypeInArgumentTypeReplacement(const QString &typeReplaced, + const AbstractMetaFunction *f); + +QString msgDuplicateBuiltInTypeEntry(const QString &name); +QString msgDuplicateTypeEntry(const QString &name); +QString msgInvalidTargetLanguageApiName(const QString &name); + +QString msgUnknownCheckFunction(const TypeEntryCPtr &t); + +QString msgArgumentClassNotFound(const AbstractMetaFunctionCPtr &func, + const TypeEntryCPtr &t); + +QString msgMissingCustomConversion(const TypeEntryCPtr &t); + +QString msgUnknownArrayPointerConversion(const QString &s); + +QString msgMissingProjectFileMarker(const QString &name, const QByteArray &startMarker); + +QString msgInvalidLanguageLevel(const QString &l); + +QString msgCannotFindImage(const QString &href, const QString &context, + const QString &candidate); + +#endif // MESSAGES_H diff --git a/sources/shiboken6/ApiExtractor/modifications.cpp b/sources/shiboken6/ApiExtractor/modifications.cpp new file mode 100644 index 000000000..d876e8035 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/modifications.cpp @@ -0,0 +1,672 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "modifications.h" +#include "codesnip.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QRegularExpression> + +#include <algorithm> +#include <limits> + +using namespace Qt::StringLiterals; + +// ---------------------- FieldModification + +class FieldModificationData : public QSharedData +{ +public: + QString m_name; + QString m_renamedToName; + bool m_readable = true; + bool m_writable = true; + bool m_removed = false; + bool m_opaqueContainer = false; + TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified; +}; + +FieldModification::FieldModification() : d(new FieldModificationData) +{ +} + +FieldModification::FieldModification(const FieldModification &) = default; +FieldModification &FieldModification::operator=(const FieldModification &) = default; +FieldModification::FieldModification(FieldModification &&) noexcept = default; +FieldModification &FieldModification::operator=(FieldModification &&) noexcept = default; +FieldModification::~FieldModification() = default; + +QString FieldModification::name() const +{ + return d->m_name; +} + +void FieldModification::setName(const QString &value) +{ + if (d->m_name != value) + d->m_name = value; +} + +bool FieldModification::isRenameModifier() const +{ + return !d->m_renamedToName.isEmpty(); +} + +QString FieldModification::renamedToName() const +{ + return d->m_renamedToName; +} + +void FieldModification::setRenamedToName(const QString &value) +{ + if (d->m_renamedToName != value) + d->m_renamedToName = value; +} + +bool FieldModification::isReadable() const +{ + return d->m_readable; +} + +void FieldModification::setReadable(bool e) +{ + if (d->m_readable != e) + d->m_readable = e; +} + +bool FieldModification::isWritable() const +{ + return d->m_writable; +} + +void FieldModification::setWritable(bool e) +{ + if (d->m_writable != e) + d->m_writable = e; +} + +bool FieldModification::isRemoved() const +{ + return d->m_removed; +} + +void FieldModification::setRemoved(bool r) +{ + if (d->m_removed != r) + d->m_removed = r; +} + +bool FieldModification::isOpaqueContainer() const +{ + return d->m_opaqueContainer; +} + +void FieldModification::setOpaqueContainer(bool r) +{ + if (d->m_opaqueContainer != r) + d->m_opaqueContainer = r; +} + +TypeSystem::SnakeCase FieldModification::snakeCase() const +{ + return d->snakeCase; +} + +void FieldModification::setSnakeCase(TypeSystem::SnakeCase s) +{ + if (d->snakeCase != s) + d->snakeCase = s; +} + +// Remove the parameter names enclosed in '@' from an added function signature +// so that it matches the C++ type signature. +static QString removeParameterNames(QString signature) +{ + while (true) { + const auto ampPos = signature.indexOf(u'@'); + if (ampPos == -1) + break; + const auto closingAmpPos = signature.indexOf(u'@', ampPos + 1); + if (closingAmpPos == -1) + break; + signature.remove(ampPos, closingAmpPos - ampPos + 1); + } + return signature; +} + +DocModification::DocModification(const QString &xpath, const QString &signature) : + m_xpath(xpath), m_signature(removeParameterNames(signature)) +{ +} + +DocModification::DocModification(TypeSystem::DocModificationMode mode, const QString &signature) : + m_signature(removeParameterNames(signature)), m_mode(mode) +{ +} + +void DocModification::setCode(const QString &code) +{ + m_code = CodeSnipAbstract::fixSpaces(code); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const ReferenceCount &r) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "ReferenceCount(" << r.varName << ", action=" << r.action << ')'; + return d; +} + +QDebug operator<<(QDebug d, const CodeSnip &s) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + const auto size = s.codeList.size(); + d << "CodeSnip(language=" << s.language << ", position=" << s.position + << ", fragments[" << size << "]="; + for (qsizetype i = 0; i < size; ++i) { + const auto &f = s.codeList.at(i); + if (i) + d << ", "; + d << '#' << i << ' '; + if (!f.instance()) { + d << '"'; + const QString &code = f.code(); + const auto lines = QStringView{code}.split(u'\n'); + for (qsizetype i = 0, size = lines.size(); i < size; ++i) { + if (i) + d << "\\n"; + d << lines.at(i).trimmed(); + } + d << '"'; + } else { + d << "template=\"" << f.instance()->name() << '"'; + } + } + d << ')'; + return d; +} + +class ArgumentModificationData : public QSharedData +{ +public: + ArgumentModificationData(int idx = -1) : index(idx), + removedDefaultExpression(false), removed(false), + noNullPointers(false), resetAfterUse(false), array(false) + {} + + QList<ReferenceCount> referenceCounts; + QString modified_type; + QString pyiType; + QString replacedDefaultExpression; + TypeSystem::Ownership m_targetOwnerShip = TypeSystem::UnspecifiedOwnership; + TypeSystem::Ownership m_nativeOwnerShip = TypeSystem::UnspecifiedOwnership; + CodeSnipList conversion_rules; + ArgumentOwner owner; + QString renamed_to; + int index = -1; + uint removedDefaultExpression : 1; + uint removed : 1; + uint noNullPointers : 1; + uint resetAfterUse : 1; + uint array : 1; +}; + +ArgumentModification::ArgumentModification() : d(new ArgumentModificationData) +{ +} + +ArgumentModification::ArgumentModification(int idx) : d(new ArgumentModificationData(idx)) +{ +} + +ArgumentModification::ArgumentModification(const ArgumentModification &) = default; +ArgumentModification &ArgumentModification::operator=(const ArgumentModification &) = default; +ArgumentModification::ArgumentModification(ArgumentModification &&) noexcept = default; +ArgumentModification &ArgumentModification::operator=(ArgumentModification &&) noexcept = default; +ArgumentModification::~ArgumentModification() = default; + +const QString &ArgumentModification::modifiedType() const +{ + return d->modified_type; +} + +void ArgumentModification::setModifiedType(const QString &value) +{ + if (d->modified_type != value) + d->modified_type = value; +} + +bool ArgumentModification::isTypeModified() const +{ + return !d->modified_type.isEmpty(); +} + +QString ArgumentModification::pyiType() const +{ + return d->pyiType; +} + +void ArgumentModification::setPyiType(const QString &value) +{ + if (d->pyiType != value) + d->pyiType = value; +} + +QString ArgumentModification::replacedDefaultExpression() const +{ + return d->replacedDefaultExpression; +} + +void ArgumentModification::setReplacedDefaultExpression(const QString &value) +{ + if (d->replacedDefaultExpression != value) + d->replacedDefaultExpression = value; +} + +TypeSystem::Ownership ArgumentModification::targetOwnerShip() const +{ + return d->m_targetOwnerShip; +} + +void ArgumentModification::setTargetOwnerShip(TypeSystem::Ownership o) +{ + if (o != d->m_targetOwnerShip) + d->m_targetOwnerShip = o; +} + +TypeSystem::Ownership ArgumentModification::nativeOwnership() const +{ + return d->m_nativeOwnerShip; +} + +void ArgumentModification::setNativeOwnership(TypeSystem::Ownership o) +{ + if (o != d->m_nativeOwnerShip) + d->m_nativeOwnerShip = o; +} + +const CodeSnipList &ArgumentModification::conversionRules() const +{ + return d->conversion_rules; +} + +CodeSnipList &ArgumentModification::conversionRules() +{ + return d->conversion_rules; +} + +ArgumentOwner ArgumentModification::owner() const +{ + return d->owner; +} + +void ArgumentModification::setOwner(const ArgumentOwner &value) +{ + d->owner = value; +} + +QString ArgumentModification::renamedToName() const +{ + return d->renamed_to; +} + +void ArgumentModification::setRenamedToName(const QString &value) +{ + if (d->renamed_to != value) + d->renamed_to = value; +} + +int ArgumentModification::index() const +{ + return d->index; +} + +void ArgumentModification::setIndex(int value) +{ + if (d->index != value) + d->index = value; +} + +bool ArgumentModification::removedDefaultExpression() const +{ + return d->removedDefaultExpression; +} + +void ArgumentModification::setRemovedDefaultExpression(const uint &value) +{ + if (d->removedDefaultExpression != value) + d->removedDefaultExpression = value; +} + +bool ArgumentModification::isRemoved() const +{ + return d->removed; +} + +void ArgumentModification::setRemoved(bool value) +{ + if (d->removed != value) + d->removed = value; +} + +bool ArgumentModification::noNullPointers() const +{ + return d->noNullPointers; +} + +void ArgumentModification::setNoNullPointers(bool value) +{ + if (d->noNullPointers != value) + d->noNullPointers = value; +} + +bool ArgumentModification::resetAfterUse() const +{ + return d->resetAfterUse; +} + +void ArgumentModification::setResetAfterUse(bool value) +{ + if (d->resetAfterUse != value) + d->resetAfterUse = value; +} + +bool ArgumentModification::isArray() const +{ + return d->array; +} + +void ArgumentModification::setArray(bool value) +{ + if (d->array != value) + d->array = value; +} + +const QList<ReferenceCount> &ArgumentModification::referenceCounts() const +{ + return d->referenceCounts; +} + +void ArgumentModification::addReferenceCount(const ReferenceCount &value) +{ + d->referenceCounts.append(value); +} + +class FunctionModificationData : public QSharedData +{ +public: + QString renamedToName; + FunctionModification::Modifiers modifiers; + CodeSnipList m_snips; + QList<ArgumentModification> m_argument_mods; + QString m_signature; + QString m_originalSignature; + QRegularExpression m_signaturePattern; + int m_overloadNumber = TypeSystem::OverloadNumberUnset; + bool removed = false; + TypeSystem::AllowThread m_allowThread = TypeSystem::AllowThread::Unspecified; + TypeSystem::ExceptionHandling m_exceptionHandling = TypeSystem::ExceptionHandling::Unspecified; + TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified; +}; + +FunctionModification::FunctionModification() : d(new FunctionModificationData) +{ +} + +FunctionModification::FunctionModification(const FunctionModification &) = default; +FunctionModification &FunctionModification::operator=(const FunctionModification &) = default; +FunctionModification::FunctionModification(FunctionModification &&) noexcept = default; +FunctionModification &FunctionModification::operator=(FunctionModification &&) noexcept = default; +FunctionModification::~FunctionModification() = default; + +void FunctionModification::formatDebug(QDebug &debug) const +{ + if (d->m_signature.isEmpty()) + debug << "pattern=\"" << d->m_signaturePattern.pattern(); + else + debug << "signature=\"" << d->m_signature; + debug << "\", modifiers=" << d->modifiers; + if (d->removed) + debug << ", removed"; + if (!d->renamedToName.isEmpty()) + debug << ", renamedToName=\"" << d->renamedToName << '"'; + if (d->m_allowThread != TypeSystem::AllowThread::Unspecified) + debug << ", allowThread=" << int(d->m_allowThread); + if (d->m_exceptionHandling != TypeSystem::ExceptionHandling::Unspecified) + debug << ", exceptionHandling=" << int(d->m_exceptionHandling); + if (!d->m_snips.isEmpty()) + debug << ", snips=(" << d->m_snips << ')'; + if (!d->m_argument_mods.isEmpty()) + debug << ", argument_mods=(" << d->m_argument_mods << ')'; +} + +QString FunctionModification::renamedToName() const +{ + return d->renamedToName; +} + +void FunctionModification::setRenamedToName(const QString &value) +{ + if (d->renamedToName != value) + d->renamedToName = value; +} + +FunctionModification::Modifiers FunctionModification::modifiers() const +{ + return d->modifiers; +} + +void FunctionModification::setModifiers(Modifiers m) +{ + if (d->modifiers != m) + d->modifiers = m; +} + +void FunctionModification::setModifierFlag(FunctionModification::ModifierFlag f) +{ + auto newMods = d->modifiers | f; + if (d->modifiers != newMods) + d->modifiers = newMods; +} + +void FunctionModification::clearModifierFlag(ModifierFlag f) +{ + auto newMods = d->modifiers & ~f; + if (d->modifiers != newMods) + d->modifiers = newMods; +} + +bool FunctionModification::isRemoved() const +{ + return d->removed; +} + +void FunctionModification::setRemoved(bool r) +{ + if (d->removed != r) + d->removed = r; +} + +const QList<ArgumentModification> &FunctionModification::argument_mods() const +{ + return d->m_argument_mods; +} + +QList<ArgumentModification> &FunctionModification::argument_mods() +{ + return d->m_argument_mods; +} + +void FunctionModification::setArgument_mods(const QList<ArgumentModification> &argument_mods) +{ + d->m_argument_mods = argument_mods; +} + +TypeSystem::SnakeCase FunctionModification::snakeCase() const +{ + return d->snakeCase; +} + +void FunctionModification::setSnakeCase(TypeSystem::SnakeCase s) +{ + if (d->snakeCase != s) + d->snakeCase = s; +} + +const CodeSnipList &FunctionModification::snips() const +{ + return d->m_snips; +} + +CodeSnipList &FunctionModification::snips() +{ + return d->m_snips; +} + +void FunctionModification::appendSnip(const CodeSnip &snip) +{ + d->m_snips.append(snip); +} + +void FunctionModification::setSnips(const CodeSnipList &snips) +{ + d->m_snips = snips; +} + +// ---------------------- FunctionModification +FunctionModification::AllowThread FunctionModification::allowThread() const +{ + return d->m_allowThread; +} + +void FunctionModification::setAllowThread(FunctionModification::AllowThread allow) +{ + if (d->m_allowThread != allow) + d->m_allowThread = allow; +} + +bool FunctionModification::matches(const QStringList &functionSignatures) const +{ + if (!d->m_signature.isEmpty()) + return functionSignatures.contains(d->m_signature); + + for (const auto &s : functionSignatures) { + if (d->m_signaturePattern.match(s).hasMatch()) + return true; + } + return false; +} + +bool FunctionModification::setSignature(const QString &s, QString *errorMessage) +{ + if (s.startsWith(u'^')) { + d->m_signaturePattern.setPattern(s); + if (!d->m_signaturePattern.isValid()) { + if (errorMessage) { + *errorMessage = u"Invalid signature pattern: \""_s + + s + u"\": "_s + d->m_signaturePattern.errorString(); + } + return false; + } + } else { + d->m_signature = s; + } + return true; +} + +QString FunctionModification::signature() const +{ + return d->m_signature.isEmpty() ? d->m_signaturePattern.pattern() : d->m_signature; +} + +void FunctionModification::setOriginalSignature(const QString &s) +{ + if (d->m_originalSignature != s) + d->m_originalSignature = s; +} + +QString FunctionModification::originalSignature() const +{ + return d->m_originalSignature; +} + +TypeSystem::ExceptionHandling FunctionModification::exceptionHandling() const +{ + return d->m_exceptionHandling; +} + +void FunctionModification::setExceptionHandling(TypeSystem::ExceptionHandling e) +{ + if (d->m_exceptionHandling != e) + d->m_exceptionHandling = e; +} + +int FunctionModification::overloadNumber() const +{ + return d->m_overloadNumber; +} + +void FunctionModification::setOverloadNumber(int overloadNumber) +{ + d->m_overloadNumber = overloadNumber; +} + +QDebug operator<<(QDebug d, const ArgumentOwner &a) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "ArgumentOwner(index=" << a.index << ", action=" << a.action << ')'; + return d; +} + +QDebug operator<<(QDebug d, const ArgumentModification &a) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "ArgumentModification(index=" << a.index(); + if (a.removedDefaultExpression()) + d << ", removedDefaultExpression"; + if (a.isRemoved()) + d << ", removed"; + if (a.noNullPointers()) + d << ", noNullPointers"; + if (a.isArray()) + d << ", array"; + if (!a.referenceCounts().isEmpty()) + d << ", referenceCounts=" << a.referenceCounts(); + if (!a.modifiedType().isEmpty()) + d << ", modified_type=\"" << a.modifiedType() << '"'; + if (!a.replacedDefaultExpression().isEmpty()) + d << ", replacedDefaultExpression=\"" << a.replacedDefaultExpression() << '"'; + if (a.targetOwnerShip() != TypeSystem::UnspecifiedOwnership) + d << ", target ownership=" << a.targetOwnerShip(); + if (a.nativeOwnership() != TypeSystem::UnspecifiedOwnership) + d << ", native ownership=" << a.nativeOwnership(); + if (!a.renamedToName().isEmpty()) + d << ", renamed_to=\"" << a.renamedToName() << '"'; + const auto &rules = a.conversionRules(); + if (!rules.isEmpty()) + d << ", conversionRules[" << rules.size() << "]=" << rules; + d << ", owner=" << a.owner() << ')'; + return d; +} + +QDebug operator<<(QDebug d, const FunctionModification &fm) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "FunctionModification("; + fm.formatDebug(d); + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/modifications.h b/sources/shiboken6/ApiExtractor/modifications.h new file mode 100644 index 000000000..27a38f1aa --- /dev/null +++ b/sources/shiboken6/ApiExtractor/modifications.h @@ -0,0 +1,342 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MODIFICATIONS_H +#define MODIFICATIONS_H + +#include "typesystem_enums.h" +#include "modifications_typedefs.h" + +#include <QtCore/QList> +#include <QtCore/QSharedDataPointer> +#include <QtCore/QString> + +class ArgumentModificationData; +class CodeSnip; +class FunctionModificationData; +class ModificationData; +class FieldModificationData; + +QT_BEGIN_NAMESPACE +class QDebug; +QT_END_NAMESPACE + +struct ReferenceCount +{ + enum Action { // 0x01 - 0xff + Add = 0x01, + AddAll = 0x02, + Remove = 0x04, + Set = 0x08, + Ignore = 0x10, + + ActionsMask = 0xff, + + Padding = 0xffffffff + }; + + QString varName; + Action action; +}; + +struct ArgumentOwner +{ + enum Action { + Invalid = 0x00, + Add = 0x01, + Remove = 0x02 + }; + enum { + InvalidIndex = -2, + ThisIndex = -1, + ReturnIndex = 0, + FirstArgumentIndex = 1 + }; + + Action action = Invalid; + int index = InvalidIndex; +}; + +class ArgumentModification +{ +public: + ArgumentModification(); + explicit ArgumentModification(int idx); + ArgumentModification(const ArgumentModification &); + ArgumentModification &operator=(const ArgumentModification &); + ArgumentModification(ArgumentModification &&) noexcept; + ArgumentModification &operator=(ArgumentModification &&) noexcept; + ~ArgumentModification(); + + // Reference count flags for this argument + const QList<ReferenceCount> &referenceCounts() const; + void addReferenceCount(const ReferenceCount &value); + + // The text given for the new type of the argument + const QString &modifiedType() const; + void setModifiedType(const QString &value); + bool isTypeModified() const; + + QString pyiType() const; + void setPyiType(const QString &value); + + // The text of the new default expression of the argument + QString replacedDefaultExpression() const; + void setReplacedDefaultExpression(const QString &value); + + // The new definition of ownership for a specific argument + + TypeSystem::Ownership targetOwnerShip() const; + void setTargetOwnerShip(TypeSystem::Ownership o); + TypeSystem::Ownership nativeOwnership() const; + void setNativeOwnership(TypeSystem::Ownership o); + + // Different conversion rules + const QList<CodeSnip> &conversionRules() const; + QList<CodeSnip> &conversionRules(); + + // QObject parent(owner) of this argument + ArgumentOwner owner() const; + void setOwner(const ArgumentOwner &value); + + // New name + QString renamedToName() const; + void setRenamedToName(const QString &value); + + int index() const; + void setIndex(int value); + + bool removedDefaultExpression() const; + void setRemovedDefaultExpression(const uint &value); + + bool isRemoved() const; + void setRemoved(bool value); + + bool noNullPointers() const; + void setNoNullPointers(bool value); + + bool resetAfterUse() const; + void setResetAfterUse(bool value); + + // consider "int*" to be "int[]" + bool isArray() const; + void setArray(bool value); + +private: + QSharedDataPointer<ArgumentModificationData> d; +}; + +class FunctionModification +{ +public: + using AllowThread = TypeSystem::AllowThread; + + FunctionModification(); + FunctionModification(const FunctionModification &); + FunctionModification &operator=(const FunctionModification &); + FunctionModification(FunctionModification &&) noexcept; + FunctionModification &operator=(FunctionModification &&) noexcept; + ~FunctionModification(); + + enum ModifierFlag { + Private = 0x0001, + Protected = 0x0002, + Public = 0x0004, + AccessModifierMask = 0x000f, + + Final = 0x0010, + NonFinal = 0x0020, + FinalMask = Final | NonFinal, + + Readable = 0x0100, + Writable = 0x0200, + + CodeInjection = 0x1000, + Rename = 0x2000, + Deprecated = 0x4000, + Undeprecated = 0x8000, + ReplaceExpression = 0x10000 + }; + + Q_DECLARE_FLAGS(Modifiers, ModifierFlag); + + QString renamedToName() const; + void setRenamedToName(const QString &value); + + Modifiers modifiers() const; + void setModifiers(Modifiers m); + void setModifierFlag(ModifierFlag f); + void clearModifierFlag(ModifierFlag f); + bool isRemoved() const; + void setRemoved(bool r); + + bool isAccessModifier() const + { + return (modifiers() & AccessModifierMask) != 0; + } + Modifiers accessModifier() const + { + return modifiers() & AccessModifierMask; + } + bool isPrivate() const + { + return accessModifier() == Private; + } + bool isProtected() const + { + return accessModifier() == Protected; + } + bool isPublic() const + { + return accessModifier() == Public; + } + bool isFinal() const + { + return modifiers().testFlag(Final); + } + bool isNonFinal() const + { + return modifiers().testFlag(NonFinal); + } + + bool isDeprecated() const + { + return modifiers().testFlag(Deprecated); + } + + bool isRenameModifier() const + { + return modifiers().testFlag(Rename); + } + + bool isRemoveModifier() const { return isRemoved(); } + + + + bool isCodeInjection() const + { + return modifiers().testFlag(CodeInjection); + } + + AllowThread allowThread() const; + void setAllowThread(AllowThread allow); + + bool matches(const QStringList &functionSignatures) const; + + bool setSignature(const QString &s, QString *errorMessage = nullptr); + QString signature() const; + + void setOriginalSignature(const QString &s); + QString originalSignature() const; + + TypeSystem::ExceptionHandling exceptionHandling() const; + void setExceptionHandling(TypeSystem::ExceptionHandling e); + + int overloadNumber() const; + void setOverloadNumber(int overloadNumber); + + const QList<CodeSnip> &snips() const; + QList<CodeSnip> &snips(); + void appendSnip(const CodeSnip &snip); + void setSnips(const QList<CodeSnip> &snips); + + const QList<ArgumentModification> &argument_mods() const; + QList<ArgumentModification> &argument_mods(); + void setArgument_mods(const QList<ArgumentModification> &argument_mods); + + TypeSystem::SnakeCase snakeCase() const; + void setSnakeCase(TypeSystem::SnakeCase s); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + QSharedDataPointer<FunctionModificationData> d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(FunctionModification::Modifiers) + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const ReferenceCount &); +QDebug operator<<(QDebug d, const CodeSnip &s); +QDebug operator<<(QDebug d, const ArgumentOwner &a); +QDebug operator<<(QDebug d, const ArgumentModification &a); +QDebug operator<<(QDebug d, const FunctionModification &fm); +#endif + +class FieldModification +{ +public: + FieldModification(); + FieldModification(const FieldModification &); + FieldModification &operator=(const FieldModification &); + FieldModification(FieldModification &&) noexcept; + FieldModification &operator=(FieldModification &&) noexcept; + ~FieldModification(); + + QString name() const; + void setName(const QString &value); + + bool isRenameModifier() const; + QString renamedToName() const; + void setRenamedToName(const QString &value); + + bool isReadable() const; + void setReadable(bool e); + + bool isWritable() const; + void setWritable(bool e); + + bool isRemoved() const; + void setRemoved(bool r); + + bool isOpaqueContainer() const; + void setOpaqueContainer(bool r); + + TypeSystem::SnakeCase snakeCase() const; + void setSnakeCase(TypeSystem::SnakeCase s); + +private: + QSharedDataPointer<FieldModificationData> d; +}; + +class DocModification +{ +public: + DocModification() = default; + explicit DocModification(const QString& xpath, const QString& signature); + explicit DocModification(TypeSystem::DocModificationMode mode, const QString& signature); + + void setCode(const QString& code); + void setCode(QStringView code) { setCode(code.toString()); } + + QString code() const + { + return m_code; + } + QString xpath() const + { + return m_xpath; + } + QString signature() const + { + return m_signature; + } + TypeSystem::DocModificationMode mode() const + { + return m_mode; + } + + TypeSystem::Language format() const { return m_format; } + void setFormat(TypeSystem::Language f) { m_format = f; } + +private: + QString m_code; + QString m_xpath; + QString m_signature; + TypeSystem::DocModificationMode m_mode = TypeSystem::DocModificationXPathReplace; + TypeSystem::Language m_format = TypeSystem::NativeCode; +}; + +#endif // MODIFICATIONS_H diff --git a/sources/shiboken6/ApiExtractor/modifications_typedefs.h b/sources/shiboken6/ApiExtractor/modifications_typedefs.h new file mode 100644 index 000000000..3b86c55d3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/modifications_typedefs.h @@ -0,0 +1,25 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MODIFICATIONS_TYPEDEFS_H +#define MODIFICATIONS_TYPEDEFS_H + +#include <QtCore/QList> + +#include <memory> + +class CodeSnip; +class DocModification; + +struct AddedFunction; +class FieldModification; +class FunctionModification; + +using AddedFunctionPtr = std::shared_ptr<AddedFunction>; +using AddedFunctionList = QList<AddedFunctionPtr>; +using CodeSnipList = QList<CodeSnip>; +using DocModificationList = QList<DocModification>; +using FieldModificationList = QList<FieldModification>; +using FunctionModificationList = QList<FunctionModification>; + +#endif // MODIFICATIONS_TYPEDEFS_H diff --git a/sources/shiboken6/ApiExtractor/namespacetypeentry.h b/sources/shiboken6/ApiExtractor/namespacetypeentry.h new file mode 100644 index 000000000..6ffd38430 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/namespacetypeentry.h @@ -0,0 +1,51 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NAMESPACETYPEENTRY_H +#define NAMESPACETYPEENTRY_H + +#include "complextypeentry.h" + +class NamespaceTypeEntryPrivate; + +class NamespaceTypeEntry : public ComplexTypeEntry +{ +public: + explicit NamespaceTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeEntry *clone() const override; + + NamespaceTypeEntryCPtr extends() const; + void setExtends(const NamespaceTypeEntryCPtr &e); + + const QRegularExpression &filePattern() const; // restrict files + void setFilePattern(const QRegularExpression &r); + + bool hasPattern() const; + + bool matchesFile(const QString &needle) const; + + bool isVisible() const; + void setVisibility(TypeSystem::Visibility v); + + // C++ 11 inline namespace, from code model + bool isInlineNamespace() const; + void setInlineNamespace(bool i); + + static bool isVisibleScope(const TypeEntryCPtr &e); + static bool isVisibleScope(const TypeEntry *e); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + + // Whether to generate "using namespace" into wrapper + bool generateUsing() const; + void setGenerateUsing(bool generateUsing); + +protected: + explicit NamespaceTypeEntry(NamespaceTypeEntryPrivate *d); +}; + +#endif // NAMESPACETYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/objecttypeentry.h b/sources/shiboken6/ApiExtractor/objecttypeentry.h new file mode 100644 index 000000000..da91e8ff4 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/objecttypeentry.h @@ -0,0 +1,21 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OBJECTTYPEENTRY_H +#define OBJECTTYPEENTRY_H + +#include "complextypeentry.h" + +class ObjectTypeEntry : public ComplexTypeEntry +{ +public: + explicit ObjectTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeEntry *clone() const override; + +protected: + explicit ObjectTypeEntry(ComplexTypeEntryPrivate *d); +}; + +#endif // OBJECTTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/optionsparser.cpp b/sources/shiboken6/ApiExtractor/optionsparser.cpp new file mode 100644 index 000000000..f2e64c7e4 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/optionsparser.cpp @@ -0,0 +1,232 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "optionsparser.h" +#include "messages.h" +#include "exception.h" + +#include <QtCore/QDir> +#include <QtCore/QTextStream> + +using namespace Qt::StringLiterals; + +template <class Stream> void formatBoolOption(Stream &s, const BoolOption &bo) +{ + switch (bo.source) { + case OptionSource::CommandLine: + s << "--"; + break; + case OptionSource::CommandLineSingleDash: + s << '-'; + break; + default: + break; + } + s << bo.option; + if (bo.source == OptionSource::ProjectFile) + s << " (project)"; +} + +template <class Stream> void formatOptionValue(Stream &s, const OptionValue &ov) +{ + switch (ov.source) { + case OptionSource::CommandLine: + s << "--"; + break; + case OptionSource::CommandLineSingleDash: + s << '-'; + break; + default: + break; + } + s << ov.option << '=' << ov.value; + if (ov.source == OptionSource::ProjectFile) + s << " (project)"; +} + +QTextStream &operator<<(QTextStream &s, const BoolOption &bo) +{ + formatBoolOption(s, bo); + return s; +} + +QTextStream &operator<<(QTextStream &s, const OptionValue &ov) +{ + formatOptionValue(s, ov); + return s; +} + +QDebug operator<<(QDebug debug, const BoolOption &bo) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + formatBoolOption(debug, bo); + return debug; +} + +QDebug operator<<(QDebug debug, const OptionValue &v) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + formatOptionValue(debug, v); + return debug; +} + +QDebug operator<<(QDebug debug, const Options &v) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "Options("; + if (!v.boolOptions.isEmpty()) + debug << "bools=" << v.boolOptions; + if (!v.valueOptions.isEmpty()) + debug << ", option values=" << v.valueOptions; + if (!v.positionalArguments.isEmpty()) + debug << ", pos=" << v.positionalArguments; + debug << ')'; + return debug; +} + +QTextStream &operator<<(QTextStream &s, const OptionDescription &od) +{ + if (!od.name.startsWith(u'-')) + s << "--"; + s << od.name; + if (od.description.isEmpty()) { // For formatting {{"-s", ""}, {"--short", "descr"}} + s << ", "; + } else { + s << '\n'; + const auto lines = QStringView{od.description}.split(u'\n'); + for (const auto &line : lines) + s << " " << line << '\n'; + s << '\n'; + } + return s; +} + +QTextStream &operator<<(QTextStream &s, const OptionDescriptions &options) +{ + s.setFieldAlignment(QTextStream::AlignLeft); + for (const auto &od : options) + s << od; + return s; +} + +OptionsParser::OptionsParser() noexcept = default; +OptionsParser::~OptionsParser() = default; + +const QString &OptionsParser::pathSyntax() +{ + static const QString result = + u"<path>["_s + QDir::listSeparator() + u"<path>"_s + + QDir::listSeparator() + u"...]"_s; + return result; +} + +bool OptionsParser::handleBoolOption(const QString &, OptionSource) +{ + return false; +} + +bool OptionsParser::handleOption(const QString &, const QString &, OptionSource) +{ + return false; +} + +void OptionsParser::process(Options *o) +{ + for (auto i = o->boolOptions.size() - 1; i >= 0; --i) { + const auto &opt = o->boolOptions.at(i); + if (handleBoolOption(opt.option, opt.source)) + o->boolOptions.removeAt(i); + } + for (auto i = o->valueOptions.size() - 1; i >= 0; --i) { + const auto &opt = o->valueOptions.at(i); + if (handleOption(opt.option, opt.value, opt.source)) + o->valueOptions.removeAt(i); + } +} + +bool OptionsParserList::handleBoolOption(const QString &key, OptionSource source) +{ + for (const auto &p : std::as_const(m_parsers)) { + if (p->handleBoolOption(key, source)) + return true; + } + return false; +} + +bool OptionsParserList::handleOption(const QString &key, const QString &value, OptionSource source) +{ + for (const auto &p : std::as_const(m_parsers)) { + if (p->handleOption(key, value, source)) + return true; + } + return false; +} + +static void processOption(const QString &o, OptionSource source, + BoolOptions *bools, OptionValues *values) +{ + const auto equals = o.indexOf(u'='); + if (equals == -1) { + bools->append({o.trimmed(), source}); + } else { + QString key = o.left(equals).trimmed(); + QString value = o.mid(equals + 1).trimmed(); + if (!value.isEmpty()) + values->append({key, value, source}); + } +} + +static void readProjectFile(const QString &name, Options *o) +{ + const auto startMarker = "[generator-project]"_ba; + + QFile file(name); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + throw Exception(msgCannotOpenForReading(file)); + + if (file.atEnd() || file.readLine().trimmed() != startMarker) + throw Exception(msgMissingProjectFileMarker(name, startMarker)); + + while (!file.atEnd()) { + const QByteArray lineB = file.readLine().trimmed(); + if (!lineB.isEmpty() && !lineB.startsWith('#')) { + processOption(QString::fromUtf8(lineB), OptionSource::ProjectFile, + &o->boolOptions, &o->valueOptions); + } + } +} + +void Options::setOptions(const QStringList &argv) +{ + const auto projectFileOption = "--project-file="_L1; + for (const auto &o : argv) { + if (o.startsWith(projectFileOption)) { + readProjectFile(o.sliced(projectFileOption.size()), this); + } else if (o.startsWith(u"--")) { + processOption(o.sliced(2), OptionSource::CommandLine, + &boolOptions, &valueOptions); + } else if (o.startsWith(u'-')) { + processOption(o.sliced(1), OptionSource::CommandLineSingleDash, + &boolOptions, &valueOptions); + } else { + positionalArguments.append(o); + } + } +} + +QString Options::msgUnprocessedOptions() const +{ + QString result; + QTextStream str(&result); + for (const auto &b : boolOptions) + str << b << ' '; + for (const auto &v : valueOptions) + str << v << ' '; + return result.trimmed(); +} diff --git a/sources/shiboken6/ApiExtractor/optionsparser.h b/sources/shiboken6/ApiExtractor/optionsparser.h new file mode 100644 index 000000000..d5557dc15 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/optionsparser.h @@ -0,0 +1,98 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OPTIONSPARSER_H +#define OPTIONSPARSER_H + +#include <QtCore/QString> +#include <QtCore/QStringList> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QTextStream) + +enum class OptionSource +{ + CommandLine, // "--option" + CommandLineSingleDash, // "-o" + ProjectFile +}; + +struct BoolOption +{ + QString option; + OptionSource source = OptionSource::CommandLine; +}; + +struct OptionValue // --option=value pair +{ + QString option; + QString value; + OptionSource source = OptionSource::CommandLine; +}; + +using BoolOptions = QList<BoolOption>; +using OptionValues = QList<OptionValue>; + +struct Options // Options from command line and project file +{ + void setOptions(const QStringList &argv); + QString msgUnprocessedOptions() const; + + BoolOptions boolOptions; + OptionValues valueOptions; + QStringList positionalArguments; +}; + +struct OptionDescription // For help formatting +{ + QString name; + QString description; +}; + +using OptionDescriptions = QList<OptionDescription>; + +QTextStream &operator<<(QTextStream &s, const BoolOption &bo); +QTextStream &operator<<(QTextStream &s, const OptionValue &ov); +QTextStream &operator<<(QTextStream &s, const OptionDescription &od); +QTextStream &operator<<(QTextStream &s, const OptionDescriptions &options); + +class OptionsParser +{ +public: + Q_DISABLE_COPY_MOVE(OptionsParser) + + virtual ~OptionsParser(); + + // Return true to indicate the option was processed. + virtual bool handleBoolOption(const QString &key, OptionSource source); + virtual bool handleOption(const QString &key, const QString &value, OptionSource source); + + void process(Options *); + + static const QString &pathSyntax(); + +protected: + OptionsParser() noexcept; +}; + +class OptionsParserList : public OptionsParser +{ +public: + using OptionsParserPtr = std::shared_ptr<OptionsParser>; + + void append(const OptionsParserPtr &parser) { m_parsers.append(parser); } + void clear() { m_parsers.clear(); } + + bool handleBoolOption(const QString &key, OptionSource source) override; + bool handleOption(const QString &key, const QString &value, OptionSource source) override; + +private: + QList<OptionsParserPtr> m_parsers; +}; + +QDebug operator<<(QDebug debug, const BoolOption &bo); +QDebug operator<<(QDebug debug, const OptionValue &v); +QDebug operator<<(QDebug debug, const Options &v); + +#endif // OPTIONSPARSER_H diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel.cpp b/sources/shiboken6/ApiExtractor/parser/codemodel.cpp new file mode 100644 index 000000000..259a706dc --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/codemodel.cpp @@ -0,0 +1,1562 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2002-2005 Roberto Raggi <roberto@kdevelop.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "codemodel.h" + +#include <sourcelocation.h> +#include <debughelpers_p.h> + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QRegularExpression> + +#include <algorithm> + +using namespace Qt::StringLiterals; + +template <class T> +static std::shared_ptr<T> findModelItem(const QList<std::shared_ptr<T> > &list, + QAnyStringView name) +{ + using ItemPtr = std::shared_ptr<T>; + auto pred = [name](const ItemPtr &item) { return item->name() == name; }; + const auto it = std::find_if(list.cbegin(), list.cend(), pred); + return it != list.cend() ? *it : ItemPtr{}; +} + +// --------------------------------------------------------------------------- + +CodeModel::CodeModel() : m_globalNamespace(new _NamespaceModelItem(this)) +{ +} + +CodeModel::~CodeModel() = default; + +NamespaceModelItem CodeModel::globalNamespace() const +{ + return m_globalNamespace; +} + +void CodeModel::addFile(const FileModelItem &item) +{ + m_files.append(item); +} + +FileModelItem CodeModel::findFile(QAnyStringView name) const +{ + return findModelItem(m_files, name); +} + +static CodeModelItem findRecursion(const ScopeModelItem &scope, + const QStringList &qualifiedName, int segment = 0) +{ + const QString &nameSegment = qualifiedName.at(segment); + if (segment == qualifiedName.size() - 1) { // Leaf item + if (ClassModelItem cs = scope->findClass(nameSegment)) + return cs; + if (EnumModelItem es = scope->findEnum(nameSegment)) + return es; + if (TypeDefModelItem tp = scope->findTypeDef(nameSegment)) + return tp; + if (TemplateTypeAliasModelItem tta = scope->findTemplateTypeAlias(nameSegment)) + return tta; + return {}; + } + if (auto nestedClass = scope->findClass(nameSegment)) + return findRecursion(nestedClass, qualifiedName, segment + 1); + if (auto namespaceItem = std::dynamic_pointer_cast<_NamespaceModelItem>(scope)) { + for (const auto &nestedNamespace : namespaceItem->namespaces()) { + if (nestedNamespace->name() == nameSegment) { + if (auto item = findRecursion(nestedNamespace, qualifiedName, segment + 1)) + return item; + } + } + } + return {}; +} + +CodeModelItem CodeModel::findItem(const QStringList &qualifiedName, const ScopeModelItem &scope) +{ + return findRecursion(scope, qualifiedName); +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<(QDebug d, Access a) +{ + QDebugStateSaver s(d); + d.noquote(); + d.nospace(); + switch (a) { + case Access::Public: + d << "public"; + break; + case Access::Protected: + d << "protected"; + break; + case Access::Private: + d << "private"; + break; + } + return d; +} + +QDebug operator<<(QDebug d, const CodeModel *m) +{ + QDebugStateSaver s(d); + d.noquote(); + d.nospace(); + d << "CodeModel("; + if (m) { + const NamespaceModelItem globalNamespaceP = m->globalNamespace(); + if (globalNamespaceP) + globalNamespaceP->formatDebug(d); + } else { + d << '0'; + } + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_CodeModelItem::_CodeModelItem(CodeModel *model, int kind) + : m_model(model), + m_kind(kind), + m_startLine(0), + m_startColumn(0), + m_endLine(0), + m_endColumn(0) +{ +} + +_CodeModelItem::_CodeModelItem(CodeModel *model, const QString &name, int kind) + : m_model(model), + m_kind(kind), + m_startLine(0), + m_startColumn(0), + m_endLine(0), + m_endColumn(0), + m_name(name) +{ +} + +_CodeModelItem::~_CodeModelItem() = default; + +int _CodeModelItem::kind() const +{ + return m_kind; +} + +QStringList _CodeModelItem::qualifiedName() const +{ + QStringList q = scope(); + + if (!name().isEmpty()) + q += name(); + + return q; +} + +QString _CodeModelItem::name() const +{ + return m_name; +} + +void _CodeModelItem::setName(const QString &name) +{ + m_name = name; +} + +QStringList _CodeModelItem::scope() const +{ + return m_scope; +} + +void _CodeModelItem::setScope(const QStringList &scope) +{ + m_scope = scope; +} + +QString _CodeModelItem::fileName() const +{ + return m_fileName; +} + +void _CodeModelItem::setFileName(const QString &fileName) +{ + m_fileName = fileName; +} + +FileModelItem _CodeModelItem::file() const +{ + return model()->findFile(fileName()); +} + +void _CodeModelItem::getStartPosition(int *line, int *column) +{ + *line = m_startLine; + *column = m_startColumn; +} + +void _CodeModelItem::setStartPosition(int line, int column) +{ + m_startLine = line; + m_startColumn = column; +} + +void _CodeModelItem::getEndPosition(int *line, int *column) +{ + *line = m_endLine; + *column = m_endColumn; +} + +void _CodeModelItem::setEndPosition(int line, int column) +{ + m_endLine = line; + m_endColumn = column; +} + +SourceLocation _CodeModelItem::sourceLocation() const +{ + return SourceLocation(m_fileName, m_startLine); +} + +const _ScopeModelItem *_CodeModelItem::enclosingScope() const +{ + return m_enclosingScope; +} + +void _CodeModelItem::setEnclosingScope(const _ScopeModelItem *s) +{ + m_enclosingScope = s; +} + +_ScopeModelItem::_ScopeModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) +{ +} + +_ScopeModelItem::_ScopeModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind) +{ +} + +#ifndef QT_NO_DEBUG_STREAM +void _CodeModelItem::formatKind(QDebug &d, int k) +{ + switch (k) { + case Kind_Argument: + d << "ArgumentModelItem"; + break; + case Kind_Class: + d << "ClassModelItem"; + break; + case Kind_Enum: + d << "EnumModelItem"; + break; + case Kind_Enumerator: + d << "EnumeratorModelItem"; + break; + case Kind_File: + d << "FileModelItem"; + break; + case Kind_Function: + d << "FunctionModelItem"; + break; + case Kind_Member: + d << "MemberModelItem"; + break; + case Kind_Namespace: + d << "NamespaceModelItem"; + break; + case Kind_Variable: + d << "VariableModelItem"; + break; + case Kind_Scope: + d << "ScopeModelItem"; + break; + case Kind_TemplateParameter: + d << "TemplateParameter"; + break; + case Kind_TypeDef: + d << "TypeDefModelItem"; + break; + case Kind_TemplateTypeAlias: + d << "TemplateTypeAliasModelItem"; + break; + default: + d << "CodeModelItem"; + break; + } +} + +void _CodeModelItem::formatDebug(QDebug &d) const +{ + d << "(\"" << name() << '"'; + if (!m_scope.isEmpty()) { + d << ", scope="; + formatSequence(d, m_scope.cbegin(), m_scope.cend(), "::"); + } + if (!m_fileName.isEmpty()) { + d << ", file=\"" << QDir::toNativeSeparators(m_fileName); + if (m_startLine > 0) + d << ':' << m_startLine; + d << '"'; + } +} + +QDebug operator<<(QDebug d, const _CodeModelItem *t) +{ + QDebugStateSaver s(d); + d.noquote(); + d.nospace(); + if (!t) { + d << "CodeModelItem(0)"; + return d; + } + _CodeModelItem::formatKind(d, t->kind()); + t->formatDebug(d); + switch (t->kind()) { + case _CodeModelItem::Kind_Class: + d << " /* class " << t->name() << " */"; + break; + case _CodeModelItem::Kind_Namespace: + d << " /* namespace " << t->name() << " */"; + break; + } + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_ClassModelItem::~_ClassModelItem() = default; + +TemplateParameterList _ClassModelItem::templateParameters() const +{ + return m_templateParameters; +} + +void _ClassModelItem::setTemplateParameters(const TemplateParameterList &templateParameters) +{ + m_templateParameters = templateParameters; +} + +bool _ClassModelItem::extendsClass(const QString &name) const +{ + for (const BaseClass &bc : m_baseClasses) { + if (bc.name == name) + return true; + } + return false; +} + +_ClassModelItem::_ClassModelItem(CodeModel *model, int kind) + : _ScopeModelItem(model, kind) +{ +} + +_ClassModelItem::_ClassModelItem(CodeModel *model, const QString &name, int kind) + : _ScopeModelItem(model, name, kind) +{ +} + +const QList<_ClassModelItem::UsingMember> &_ClassModelItem::usingMembers() const +{ + return m_usingMembers; +} + +void _ClassModelItem::addUsingMember(const QString &className, + const QString &memberName, + Access accessPolicy) +{ + m_usingMembers.append({className, memberName, accessPolicy}); +} + +void _ClassModelItem::setClassType(CodeModel::ClassType type) +{ + m_classType = type; +} + +CodeModel::ClassType _ClassModelItem::classType() const +{ + return m_classType; +} + +void _ClassModelItem::addPropertyDeclaration(const QString &propertyDeclaration) +{ + m_propertyDeclarations << propertyDeclaration; +} + +bool _ClassModelItem::isEmpty() const +{ + return _ScopeModelItem::isEmpty() && m_propertyDeclarations.isEmpty(); +} + +bool _ClassModelItem::isTemplate() const +{ + return !m_templateParameters.isEmpty(); +} + +#ifndef QT_NO_DEBUG_STREAM +template <class List> +static void formatModelItemList(QDebug &d, const char *prefix, const List &l, + const char *separator = ", ") +{ + if (const auto size = l.size()) { + d << prefix << '[' << size << "]("; + for (qsizetype i = 0; i < size; ++i) { + if (i) + d << separator; + l.at(i)->formatDebug(d); + } + d << ')'; + } +} + +void _ClassModelItem::formatDebug(QDebug &d) const +{ + _ScopeModelItem::formatDebug(d); + if (!m_baseClasses.isEmpty()) { + if (m_final) + d << " [final]"; + d << ", inherits="; + d << ", inherits="; + for (qsizetype i = 0, size = m_baseClasses.size(); i < size; ++i) { + if (i) + d << ", "; + d << m_baseClasses.at(i).name << " (" << m_baseClasses.at(i).accessPolicy << ')'; + } + } + for (const auto &im : m_usingMembers) + d << ", using " << im.className << "::" << im.memberName + << " (" << im.access << ')'; + formatModelItemList(d, ", templateParameters=", m_templateParameters); + formatScopeItemsDebug(d); + if (!m_propertyDeclarations.isEmpty()) + d << ", Properties=" << m_propertyDeclarations; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +FunctionModelItem _ScopeModelItem::declaredFunction(const FunctionModelItem &item) +{ + for (const FunctionModelItem &fun : std::as_const(m_functions)) { + if (fun->name() == item->name() && fun->isSimilar(item)) + return fun; + + } + return {}; +} + +_ScopeModelItem::~_ScopeModelItem() = default; + +void _ScopeModelItem::addEnumsDeclaration(const QString &enumsDeclaration) +{ + m_enumsDeclarations << enumsDeclaration; +} + +void _ScopeModelItem::addClass(const ClassModelItem &item) +{ + m_classes.append(item); + item->setEnclosingScope(this); +} + +void _ScopeModelItem::addFunction(const FunctionModelItem &item) +{ + m_functions.append(item); + item->setEnclosingScope(this); +} + +void _ScopeModelItem::addVariable(const VariableModelItem &item) +{ + m_variables.append(item); + item->setEnclosingScope(this); +} + +void _ScopeModelItem::addTypeDef(const TypeDefModelItem &item) +{ + m_typeDefs.append(item); + item->setEnclosingScope(this); +} + +void _ScopeModelItem::addTemplateTypeAlias(const TemplateTypeAliasModelItem &item) +{ + m_templateTypeAliases.append(item); + item->setEnclosingScope(this); +} + +qsizetype _ScopeModelItem::indexOfEnum(const QString &name) const +{ + for (qsizetype i = 0, size = m_enums.size(); i < size; ++i) { + if (m_enums.at(i)->name() == name) + return i; + } + return -1; +} + +void _ScopeModelItem::addEnum(const EnumModelItem &item) +{ + item->setEnclosingScope(this); + // A forward declaration of an enum ("enum class Foo;") is undistinguishable + // from an enum without values ("enum class QCborTag {}"), so, add all + // enums and replace existing ones without values by ones with values. + const int index = indexOfEnum(item->name()); + if (index >= 0) { + if (item->hasValues() && !m_enums.at(index)->hasValues()) + m_enums[index] = item; + return; + } + m_enums.append(item); +} + +void _ScopeModelItem::appendScope(const _ScopeModelItem &other) +{ + m_classes += other.m_classes; + m_enums += other.m_enums; + m_typeDefs += other.m_typeDefs; + m_templateTypeAliases += other.m_templateTypeAliases; + m_variables += other.m_variables; + m_functions += other.m_functions; + m_enumsDeclarations += other.m_enumsDeclarations; +} + +bool _ScopeModelItem::isEmpty() const +{ + return m_classes.isEmpty() && m_enums.isEmpty() + && m_typeDefs.isEmpty() && m_templateTypeAliases.isEmpty() + && m_variables.isEmpty() && m_functions.isEmpty() + && m_enumsDeclarations.isEmpty(); +} + +/* This function removes MSVC export declarations of non-type template + * specializations (see below code from photon.h) for which + * clang_isCursorDefinition() returns true, causing them to be added as + * definitions of empty classes shadowing the template definition depending + * on QHash seed values. + +template <int N> class Tpl +{ +public: +... +}; + +#ifdef WIN32 +template class LIBSAMPLE_EXPORT Tpl<54>; +*/ +void _ScopeModelItem::purgeClassDeclarations() +{ + for (auto i = m_classes.size() - 1; i >= 0; --i) { + auto klass = m_classes.at(i); + // For an empty class, check if there is a matching template + // definition, and remove it if this is the case. + if (!klass->isTemplate() && klass->isEmpty()) { + const QString definitionPrefix = klass->name() + u'<'; + const bool definitionFound = + std::any_of(m_classes.cbegin(), m_classes.cend(), + [definitionPrefix] (const ClassModelItem &c) { + return c->isTemplate() && !c->isEmpty() + && c->name().startsWith(definitionPrefix); + }); + if (definitionFound) + m_classes.removeAt(i); + } + } +} + +#ifndef QT_NO_DEBUG_STREAM +template <class Hash> +static void formatScopeHash(QDebug &d, const char *prefix, const Hash &h, + const char *separator = ", ", + bool trailingNewLine = false) +{ + if (!h.isEmpty()) { + d << prefix << '[' << h.size() << "]("; + const auto begin = h.cbegin(); + for (auto it = begin, end = h.cend(); it != end; ++it) { // Omit the names as they are repeated + if (it != begin) + d << separator; + d << it.value().data(); + } + d << ')'; + if (trailingNewLine) + d << '\n'; + } +} + +template <class List> +static void formatScopeList(QDebug &d, const char *prefix, const List &l, + const char *separator = ", ", + bool trailingNewLine = false) +{ + if (!l.isEmpty()) { + d << prefix << '[' << l.size() << "]("; + formatPtrSequence(d, l.begin(), l.end(), separator); + d << ')'; + if (trailingNewLine) + d << '\n'; + } +} + +void _ScopeModelItem::formatScopeItemsDebug(QDebug &d) const +{ + formatScopeList(d, ", classes=", m_classes, "\n", true); + formatScopeList(d, ", enums=", m_enums, "\n", true); + formatScopeList(d, ", aliases=", m_typeDefs, "\n", true); + formatScopeList(d, ", template type aliases=", m_templateTypeAliases, "\n", true); + formatScopeList(d, ", functions=", m_functions, "\n", true); + formatScopeList(d, ", variables=", m_variables); +} + +void _ScopeModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + formatScopeItemsDebug(d); +} +#endif // !QT_NO_DEBUG_STREAM + +// Predicate to match a non-template class name against the class list. +// "Vector" should match "Vector" as well as "Vector<T>" (as seen for methods +// from within the class "Vector"). +static bool matchClassNameNonTemplatePart(const ClassModelItem &item, const QString &name) +{ + const QString &itemName = item->name(); + if (!itemName.startsWith(name)) + return false; + return itemName.size() == name.size() || itemName.at(name.size()) == u'<'; +} + +ClassModelItem _ScopeModelItem::findClass(const QString &name) const +{ + // A fully qualified template is matched by name only + const ClassList::const_iterator it = name.contains(u'<') + ? std::find_if(m_classes.begin(), m_classes.end(), + [&name](const ClassModelItem &item) { + return item->name() == name; }) + : std::find_if(m_classes.begin(), m_classes.end(), + [&name](const ClassModelItem &item) { + return matchClassNameNonTemplatePart(item, name); }); + return it != m_classes.end() ? *it : ClassModelItem(); +} + +VariableModelItem _ScopeModelItem::findVariable(QAnyStringView name) const +{ + return findModelItem(m_variables, name); +} + +TypeDefModelItem _ScopeModelItem::findTypeDef(QAnyStringView name) const +{ + return findModelItem(m_typeDefs, name); +} + +TemplateTypeAliasModelItem _ScopeModelItem::findTemplateTypeAlias(QAnyStringView name) const +{ + return findModelItem(m_templateTypeAliases, name); +} + +EnumModelItem _ScopeModelItem::findEnum(QAnyStringView name) const +{ + return findModelItem(m_enums, name); +} + +_ScopeModelItem::FindEnumByValueReturn + _ScopeModelItem::findEnumByValueHelper(QStringView fullValue, + QStringView enumValue) const +{ + const bool unqualified = fullValue.size() == enumValue.size(); + QString scopePrefix = scope().join(u"::"); + if (!scopePrefix.isEmpty()) + scopePrefix += u"::"_s; + scopePrefix += name() + u"::"_s; + + for (const auto &e : m_enums) { + const auto index = e->indexOfValue(enumValue); + if (index != -1) { + QString fullyQualifiedName = scopePrefix; + if (e->enumKind() != AnonymousEnum) + fullyQualifiedName += e->name() + u"::"_s; + fullyQualifiedName += e->enumerators().at(index)->name(); + if (unqualified || fullyQualifiedName.endsWith(fullValue)) + return {e, fullyQualifiedName}; + // For standard enums, check the name without enum name + if (e->enumKind() == CEnum) { + const QString qualifiedName = + scopePrefix + e->enumerators().at(index)->name(); + if (qualifiedName.endsWith(fullValue)) + return {e, fullyQualifiedName}; + } + } + } + + return {}; +} + +// Helper to recursively find the scope of an enum value +_ScopeModelItem::FindEnumByValueReturn + _ScopeModelItem::findEnumByValueRecursion(const _ScopeModelItem *scope, + QStringView fullValue, + QStringView enumValue, + bool searchSiblingNamespaces) +{ + if (const auto e = scope->findEnumByValueHelper(fullValue, enumValue)) + return e; + + if (auto *enclosingScope = scope->enclosingScope()) { + // The enclosing scope may have several sibling namespaces of that name. + if (searchSiblingNamespaces && scope->kind() == Kind_Namespace) { + if (auto *enclosingNamespace = dynamic_cast<const _NamespaceModelItem *>(enclosingScope)) { + for (const auto &sibling : enclosingNamespace->namespaces()) { + if (sibling.get() != scope && sibling->name() == scope->name()) { + if (const auto e = findEnumByValueRecursion(sibling.get(), + fullValue, enumValue, false)) { + return e; + } + } + } + } + } + + if (const auto e = findEnumByValueRecursion(enclosingScope, fullValue, enumValue)) + return e; + } + + // PYSIDE-331: We need to also search the base classes. + if (auto *classItem = dynamic_cast<const _ClassModelItem *>(scope)) { + for (const auto &base : classItem->baseClasses()) { + if (base.klass) { + auto *c = base.klass.get(); + if (const auto e = findEnumByValueRecursion(c, fullValue, enumValue)) + return e; + } + } + } + + return {}; +} + +_ScopeModelItem::FindEnumByValueReturn + _ScopeModelItem::findEnumByValue(QStringView value) const +{ + const auto lastQualifier = value.lastIndexOf(u"::"); + const auto enumValue = lastQualifier == -1 + ? value : value.mid(lastQualifier + 2); + return findEnumByValueRecursion(this, value, enumValue); +} + +FunctionList _ScopeModelItem::findFunctions(QAnyStringView name) const +{ + FunctionList result; + for (const FunctionModelItem &func : m_functions) { + if (func->name() == name) + result.append(func); + } + return result; +} + +// --------------------------------------------------------------------------- +_NamespaceModelItem::_NamespaceModelItem(CodeModel *model, int kind) + : _ScopeModelItem(model, kind) +{ +} + +_NamespaceModelItem::_NamespaceModelItem(CodeModel *model, const QString &name, int kind) + : _ScopeModelItem(model, name, kind) +{ +} + +_NamespaceModelItem::~_NamespaceModelItem() = default; + +void _NamespaceModelItem::addNamespace(NamespaceModelItem item) +{ + item->setEnclosingScope(this); + m_namespaces.append(item); +} + +NamespaceModelItem _NamespaceModelItem::findNamespace(QAnyStringView name) const +{ + return findModelItem(m_namespaces, name); +} + +_FileModelItem::~_FileModelItem() = default; + +void _NamespaceModelItem::appendNamespace(const _NamespaceModelItem &other) +{ + appendScope(other); + m_namespaces += other.m_namespaces; +} + +#ifndef QT_NO_DEBUG_STREAM +void _NamespaceModelItem::formatDebug(QDebug &d) const +{ + _ScopeModelItem::formatDebug(d); + switch (m_type) { + case NamespaceType::Default: + break; + case NamespaceType::Anonymous: + d << ", anonymous"; + break; + case NamespaceType::Inline: + d << ", inline"; + break; + } + formatScopeList(d, ", namespaces=", m_namespaces); +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_ArgumentModelItem::_ArgumentModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) +{ +} + +_ArgumentModelItem::_ArgumentModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind) +{ +} + +_ArgumentModelItem::~_ArgumentModelItem() = default; + +TypeInfo _ArgumentModelItem::type() const +{ + return m_type; +} + +void _ArgumentModelItem::setType(const TypeInfo &type) +{ + m_type = type; +} + +bool _ArgumentModelItem::defaultValue() const +{ + return m_defaultValue; +} + +void _ArgumentModelItem::setDefaultValue(bool defaultValue) +{ + m_defaultValue = defaultValue; +} + +bool _ArgumentModelItem::scopeResolution() const +{ + return m_scopeResolution; +} + +void _ArgumentModelItem::setScopeResolution(bool v) +{ + m_scopeResolution = v; +} + +#ifndef QT_NO_DEBUG_STREAM +void _ArgumentModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + d << ", type=" << m_type; + if (m_scopeResolution) + d << ", [m_scope resolution]"; + if (m_defaultValue) + d << ", defaultValue=\"" << m_defaultValueExpression << '"'; +} +#endif // !QT_NO_DEBUG_STREAM +// --------------------------------------------------------------------------- +_FunctionModelItem::~_FunctionModelItem() = default; + +bool _FunctionModelItem::isSimilar(const FunctionModelItem &other) const +{ + if (name() != other->name()) + return false; + + if (isConstant() != other->isConstant()) + return false; + + if (isVariadics() != other->isVariadics()) + return false; + + if (arguments().size() != other->arguments().size()) + return false; + + // ### check the template parameters + + for (qsizetype i = 0; i < arguments().size(); ++i) { + ArgumentModelItem arg1 = arguments().at(i); + ArgumentModelItem arg2 = other->arguments().at(i); + + if (arg1->type() != arg2->type()) + return false; + } + + return true; +} + +_FunctionModelItem::_FunctionModelItem(CodeModel *model, int kind) + : _MemberModelItem(model, kind), m_flags(0) +{ +} + +_FunctionModelItem::_FunctionModelItem(CodeModel *model, const QString &name, int kind) + : _MemberModelItem(model, name, kind), m_flags(0) +{ +} + +ArgumentList _FunctionModelItem::arguments() const +{ + return m_arguments; +} + +void _FunctionModelItem::addArgument(const ArgumentModelItem& item) +{ + m_arguments.append(item); +} + +CodeModel::FunctionType _FunctionModelItem::functionType() const +{ + return m_functionType; +} + +void _FunctionModelItem::setFunctionType(CodeModel::FunctionType functionType) +{ + m_functionType = functionType; +} + +bool _FunctionModelItem::isVariadics() const +{ + return m_isVariadics; +} + +void _FunctionModelItem::setVariadics(bool isVariadics) +{ + m_isVariadics = isVariadics; +} + +bool _FunctionModelItem::scopeResolution() const +{ + return m_scopeResolution; +} + +void _FunctionModelItem::setScopeResolution(bool v) +{ + m_scopeResolution = v; +} + +bool _FunctionModelItem::isDefaultConstructor() const +{ + return m_functionType == CodeModel::Constructor + && (m_arguments.isEmpty() || m_arguments.constFirst()->defaultValue()); +} + +bool _FunctionModelItem::isSpaceshipOperator() const +{ + return m_functionType == CodeModel::ComparisonOperator + && name() == u"operator<=>"; +} + +bool _FunctionModelItem::isNoExcept() const +{ + return m_exceptionSpecification == ExceptionSpecification::NoExcept; +} + +bool _FunctionModelItem::isOperator() const +{ + bool result = false; + switch (m_functionType) { + case CodeModel::CallOperator: + case CodeModel::ConversionOperator: + case CodeModel::DereferenceOperator: + case CodeModel::ReferenceOperator: + case CodeModel::ArrowOperator: + case CodeModel::ArithmeticOperator: + case CodeModel::IncrementOperator: + case CodeModel::DecrementOperator: + case CodeModel::BitwiseOperator: + case CodeModel::LogicalOperator: + case CodeModel::ShiftOperator: + case CodeModel::SubscriptOperator: + case CodeModel::ComparisonOperator: + result = true; + break; + default: + break; + } + return result; +} + +ExceptionSpecification _FunctionModelItem::exceptionSpecification() const +{ + return m_exceptionSpecification; +} + +void _FunctionModelItem::setExceptionSpecification(ExceptionSpecification e) +{ + m_exceptionSpecification = e; +} + +bool _FunctionModelItem::isDeleted() const +{ + return m_isDeleted; +} + +void _FunctionModelItem::setDeleted(bool d) +{ + m_isDeleted = d; +} + +bool _FunctionModelItem::isInline() const +{ + return m_isInline; +} + +void _FunctionModelItem::setInline(bool isInline) +{ + m_isInline = isInline; +} + +bool _FunctionModelItem::isHiddenFriend() const +{ + return m_isHiddenFriend; +} + +void _FunctionModelItem::setHiddenFriend(bool f) +{ + m_isHiddenFriend = f; +} + +QString _FunctionModelItem::typeSystemSignature() const // For dumping out type system files +{ + QString result; + QTextStream str(&result); + str << name() << '('; + for (qsizetype a = 0, size = m_arguments.size(); a < size; ++a) { + if (a) + str << ','; + m_arguments.at(a)->type().formatTypeSystemSignature(str); + } + str << ')'; + return result; +} + +using NameFunctionTypeHash = QHash<QStringView, CodeModel::FunctionType>; + +static const NameFunctionTypeHash &nameToOperatorFunction() +{ + static const NameFunctionTypeHash result = { + {u"operator=", CodeModel::AssignmentOperator}, + {u"operator+", CodeModel::ArithmeticOperator}, + {u"operator+=", CodeModel::ArithmeticOperator}, + {u"operator-", CodeModel::ArithmeticOperator}, + {u"operator-=", CodeModel::ArithmeticOperator}, + {u"operator*", CodeModel::ArithmeticOperator}, + {u"operator*=", CodeModel::ArithmeticOperator}, + {u"operator/", CodeModel::ArithmeticOperator}, + {u"operator/=", CodeModel::ArithmeticOperator}, + {u"operator%", CodeModel::ArithmeticOperator}, + {u"operator%=", CodeModel::ArithmeticOperator}, + {u"operator++", CodeModel::IncrementOperator}, + {u"operator--", CodeModel::DecrementOperator}, + {u"operator&", CodeModel::BitwiseOperator}, + {u"operator&=", CodeModel::BitwiseOperator}, + {u"operator|", CodeModel::BitwiseOperator}, + {u"operator|=", CodeModel::BitwiseOperator}, + {u"operator^", CodeModel::BitwiseOperator}, + {u"operator^=", CodeModel::BitwiseOperator}, + {u"operator~", CodeModel::BitwiseOperator}, + {u"operator<<", CodeModel::ShiftOperator}, + {u"operator<<=", CodeModel::ShiftOperator}, + {u"operator>>", CodeModel::ShiftOperator}, + {u"operator>>=", CodeModel::ShiftOperator}, + {u"operator<", CodeModel::ComparisonOperator}, + {u"operator<=", CodeModel::ComparisonOperator}, + {u"operator>", CodeModel::ComparisonOperator}, + {u"operator>=", CodeModel::ComparisonOperator}, + {u"operator==", CodeModel::ComparisonOperator}, + {u"operator!=", CodeModel::ComparisonOperator}, + {u"operator<=>", CodeModel::ComparisonOperator}, + {u"operator!", CodeModel::LogicalOperator}, + {u"operator&&", CodeModel::LogicalOperator}, + {u"operator||", CodeModel::LogicalOperator}, + {u"operator[]", CodeModel::SubscriptOperator}, + {u"operator()", CodeModel::CallOperator}, // Can be void + {u"operator->", CodeModel::ArrowOperator} + }; + return result; +} + +std::optional<CodeModel::FunctionType> _FunctionModelItem::functionTypeFromName(QStringView name) +{ + const auto it = nameToOperatorFunction().constFind(name); + if (it != nameToOperatorFunction().constEnd()) + return it.value(); + // This check is only for added functions. Clang detects this + // by cursor type CXCursor_ConversionFunction. + if (name.startsWith(u"operator ")) + return CodeModel::ConversionOperator; + return {}; +} + +// Check for operators, etc. unless it is a specific type like a constructor +CodeModel::FunctionType _FunctionModelItem::_determineTypeHelper() const +{ + switch (m_functionType) { + case CodeModel::Constructor: + case CodeModel::CopyConstructor: + case CodeModel::MoveConstructor: + case CodeModel::Destructor: + case CodeModel::Signal: + case CodeModel::Slot: + return m_functionType; // nothing to do here + default: + break; + } + const QString &functionName = name(); + const auto newTypeOpt = _FunctionModelItem::functionTypeFromName(functionName); + if (!newTypeOpt.has_value()) + return m_functionType; + + auto newType = newTypeOpt.value(); + // It's some sort of dereference operator?! + if (m_arguments.isEmpty()) { + switch (newType) { + case CodeModel::ArithmeticOperator: + if (functionName == u"operator*") + return CodeModel::DereferenceOperator; + break; + case CodeModel::BitwiseOperator: + if (functionName == u"operator&") + return CodeModel::ReferenceOperator; + break; + default: + break; + } + } + return newType; +} + +void _FunctionModelItem::_determineType() +{ + m_functionType = _determineTypeHelper(); +} + +#ifndef QT_NO_DEBUG_STREAM +void _FunctionModelItem::formatDebug(QDebug &d) const +{ + _MemberModelItem::formatDebug(d); + d << ", type=" << m_functionType << ", exspec=" << int(m_exceptionSpecification); + if (m_isDeleted) + d << " [deleted!]"; + if (m_isInline) + d << " [inline]"; + if (m_attributes.testFlag(FunctionAttribute::Virtual)) + d << " [virtual]"; + if (m_attributes.testFlag(FunctionAttribute::Override)) + d << " [override]"; + if (m_attributes.testFlag(FunctionAttribute::Deprecated)) + d << " [deprecated]"; + if (m_attributes.testFlag(FunctionAttribute::Final)) + d << " [final]"; + if (m_attributes.testFlag(FunctionAttribute::Abstract)) + d << " [abstract]"; + if (m_attributes.testFlag(FunctionAttribute::Explicit)) + d << " [explicit]"; + if (m_isInvokable) + d << " [invokable]"; + if (m_scopeResolution) + d << " [scope resolution]"; + formatModelItemList(d, ", arguments=", m_arguments); + if (m_isVariadics) + d << ",..."; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_TypeDefModelItem::_TypeDefModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) +{ +} + +_TypeDefModelItem::_TypeDefModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind) +{ +} + +TypeInfo _TypeDefModelItem::type() const +{ + return m_type; +} + +void _TypeDefModelItem::setType(const TypeInfo &type) +{ + m_type = type; +} + +#ifndef QT_NO_DEBUG_STREAM +void _TypeDefModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + d << ", type=" << m_type; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- + +_TemplateTypeAliasModelItem::_TemplateTypeAliasModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) {} + +_TemplateTypeAliasModelItem::_TemplateTypeAliasModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind) {} + +TemplateParameterList _TemplateTypeAliasModelItem::templateParameters() const +{ + return m_templateParameters; +} + +void _TemplateTypeAliasModelItem::addTemplateParameter(const TemplateParameterModelItem &templateParameter) +{ + m_templateParameters.append(templateParameter); +} + +TypeInfo _TemplateTypeAliasModelItem::type() const +{ + return m_type; +} + +void _TemplateTypeAliasModelItem::setType(const TypeInfo &type) +{ + m_type = type; +} + +#ifndef QT_NO_DEBUG_STREAM +void _TemplateTypeAliasModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + d << ", <"; + for (qsizetype i = 0, count = m_templateParameters.size(); i < count; ++i) { + if (i) + d << ", "; + d << m_templateParameters.at(i)->name(); + } + d << ">, type=" << m_type; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_EnumModelItem::_EnumModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind) +{ +} + +_EnumModelItem::_EnumModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) +{ +} + +Access _EnumModelItem::accessPolicy() const +{ + return m_accessPolicy; +} + +_EnumModelItem::~_EnumModelItem() = default; + +void _EnumModelItem::setAccessPolicy(Access accessPolicy) +{ + m_accessPolicy = accessPolicy; +} + +EnumeratorList _EnumModelItem::enumerators() const +{ + return m_enumerators; +} + +void _EnumModelItem::addEnumerator(const EnumeratorModelItem &item) +{ + m_enumerators.append(item); +} + +qsizetype _EnumModelItem::indexOfValue(QStringView value) const +{ + for (qsizetype i = 0, size = m_enumerators.size(); i < size; ++i) { + if (m_enumerators.at(i)->name() == value) + return i; + } + return -1; +} + +bool _EnumModelItem::isSigned() const +{ + return m_signed; +} + +void _EnumModelItem::setSigned(bool s) +{ + m_signed = s; +} + +QString _EnumModelItem::underlyingType() const +{ + return m_underlyingType; +} + +void _EnumModelItem::setUnderlyingType(const QString &underlyingType) +{ + m_underlyingType = underlyingType; +} + +bool _EnumModelItem::isDeprecated() const +{ + return m_deprecated; +} + +void _EnumModelItem::setDeprecated(bool d) +{ + m_deprecated = d; +} + +#ifndef QT_NO_DEBUG_STREAM +void _EnumModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + switch (m_enumKind) { + case CEnum: + break; + case AnonymousEnum: + d << " (anonymous)"; + break; + case EnumClass: + d << " (class)"; + break; + } + if (m_deprecated) + d << " (deprecated)"; + if (!m_signed) + d << " (unsigned)"; + formatModelItemList(d, ", enumerators=", m_enumerators); +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_EnumeratorModelItem::~_EnumeratorModelItem() = default; + +_EnumeratorModelItem::_EnumeratorModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) +{ +} + +_EnumeratorModelItem::_EnumeratorModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind) +{ +} + +QString _EnumeratorModelItem::stringValue() const +{ + return m_stringValue; +} + +void _EnumeratorModelItem::setStringValue(const QString &value) +{ + m_stringValue = value; +} + +bool _EnumeratorModelItem::isDeprecated() const +{ + return m_deprecated; +} + +void _EnumeratorModelItem::setDeprecated(bool d) +{ + m_deprecated = d; +} + +#ifndef QT_NO_DEBUG_STREAM +void _EnumeratorModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + d << ", value=" << m_value << ", stringValue=\"" << m_stringValue << '"'; + if (m_deprecated) + d << " (deprecated)"; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +_TemplateParameterModelItem::~_TemplateParameterModelItem() = default; + +_TemplateParameterModelItem::_TemplateParameterModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind) +{ +} + +_TemplateParameterModelItem::_TemplateParameterModelItem(CodeModel *model, + const QString &name, int kind) + : _CodeModelItem(model, name, kind) +{ +} + +TypeInfo _TemplateParameterModelItem::type() const +{ + return m_type; +} + +void _TemplateParameterModelItem::setType(const TypeInfo &type) +{ + m_type = type; +} + +bool _TemplateParameterModelItem::defaultValue() const +{ + return m_defaultValue; +} + +void _TemplateParameterModelItem::setDefaultValue(bool defaultValue) +{ + m_defaultValue = defaultValue; +} + +#ifndef QT_NO_DEBUG_STREAM +void _TemplateParameterModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + d << ", type=" << m_type; + if (m_defaultValue) + d << " [defaultValue]"; +} +#endif // !QT_NO_DEBUG_STREAM + +// --------------------------------------------------------------------------- +TypeInfo _MemberModelItem::type() const +{ + return m_type; +} + +void _MemberModelItem::setType(const TypeInfo &type) +{ + m_type = type; +} + +Access _MemberModelItem::accessPolicy() const +{ + return m_accessPolicy; +} + +_MemberModelItem::~_MemberModelItem() = default; + +void _MemberModelItem::setAccessPolicy(Access accessPolicy) +{ + m_accessPolicy = accessPolicy; +} + +bool _MemberModelItem::isStatic() const +{ + return m_isStatic; +} + +void _MemberModelItem::setStatic(bool isStatic) +{ + m_isStatic = isStatic; +} + +_MemberModelItem::_MemberModelItem(CodeModel *model, int kind) + : _CodeModelItem(model, kind), m_flags(0) +{ +} + +_MemberModelItem::_MemberModelItem(CodeModel *model, const QString &name, int kind) + : _CodeModelItem(model, name, kind), m_flags(0) +{ +} + +bool _MemberModelItem::isConstant() const +{ + return m_isConstant; +} + +void _MemberModelItem::setConstant(bool isConstant) +{ + m_isConstant = isConstant; +} + +bool _MemberModelItem::isVolatile() const +{ + return m_isVolatile; +} + +void _MemberModelItem::setVolatile(bool isVolatile) +{ + m_isVolatile = isVolatile; +} + +bool _MemberModelItem::isAuto() const +{ + return m_isAuto; +} + +void _MemberModelItem::setAuto(bool isAuto) +{ + m_isAuto = isAuto; +} + +bool _MemberModelItem::isFriend() const +{ + return m_isFriend; +} + +void _MemberModelItem::setFriend(bool isFriend) +{ + m_isFriend = isFriend; +} + +bool _MemberModelItem::isRegister() const +{ + return m_isRegister; +} + +void _MemberModelItem::setRegister(bool isRegister) +{ + m_isRegister = isRegister; +} + +bool _MemberModelItem::isExtern() const +{ + return m_isExtern; +} + +void _MemberModelItem::setExtern(bool isExtern) +{ + m_isExtern = isExtern; +} + +bool _MemberModelItem::isMutable() const +{ + return m_isMutable; +} + +void _MemberModelItem::setMutable(bool isMutable) +{ + m_isMutable = isMutable; +} + +#ifndef QT_NO_DEBUG_STREAM +void _MemberModelItem::formatDebug(QDebug &d) const +{ + _CodeModelItem::formatDebug(d); + d << ", " << m_accessPolicy << ", type="; + if (m_isConstant) + d << "const "; + if (m_isVolatile) + d << "volatile "; + if (m_isStatic) + d << "static "; + if (m_isAuto) + d << "auto "; + if (m_isFriend) + d << "friend "; + if (m_isRegister) + d << "register "; + if (m_isExtern) + d << "extern "; + if (m_isMutable) + d << "mutable "; + d << m_type; + formatScopeList(d, ", templateParameters", m_templateParameters); +} +#endif // !QT_NO_DEBUG_STREAM + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel.h b/sources/shiboken6/ApiExtractor/parser/codemodel.h new file mode 100644 index 000000000..b31c09163 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/codemodel.h @@ -0,0 +1,700 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2002-2005 Roberto Raggi <roberto@kdevelop.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#ifndef CODEMODEL_H +#define CODEMODEL_H + +#include "codemodel_fwd.h" +#include "codemodel_enums.h" +#include "enumvalue.h" +#include "typeinfo.h" + +#include <QtCore/QHash> +#include <QtCore/QSet> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtCore/QList> +#include <QtCore/QWeakPointer> + +#include <optional> +#include <utility> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +#define DECLARE_MODEL_NODE(k) \ + enum { __node_kind = Kind_##k }; + +class SourceLocation; + +class CodeModel +{ + Q_GADGET +public: + Q_DISABLE_COPY_MOVE(CodeModel) + + enum FunctionType { + Normal, + Constructor, + CopyConstructor, + MoveConstructor, + Destructor, + Signal, + Slot, + AssignmentOperator, + CallOperator, + ConversionOperator, + DereferenceOperator, // Iterator's operator * + ReferenceOperator, // operator & + ArrowOperator, + ArithmeticOperator, + IncrementOperator, + DecrementOperator, + BitwiseOperator, + LogicalOperator, + ShiftOperator, + SubscriptOperator, + ComparisonOperator + }; + Q_ENUM(FunctionType) + + enum ClassType { + Class, + Struct, + Union + }; + Q_ENUM(ClassType) + +public: + CodeModel(); + virtual ~CodeModel(); + + FileList files() const { return m_files; } + NamespaceModelItem globalNamespace() const; + + void addFile(const FileModelItem &item); + FileModelItem findFile(QAnyStringView name) const; + + static CodeModelItem findItem(const QStringList &qualifiedName, + const ScopeModelItem &scope); + +private: + FileList m_files; + NamespaceModelItem m_globalNamespace; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, Access a); +QDebug operator<<(QDebug d, const CodeModel *m); +#endif + +class _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_CodeModelItem) + + enum Kind { + /* These are bit-flags resembling inheritance */ + Kind_Scope = 0x1, + Kind_Namespace = 0x2 | Kind_Scope, + Kind_Member = 0x4, + Kind_Function = 0x8 | Kind_Member, + KindMask = 0xf, + + /* These are for classes that are not inherited from */ + FirstKind = 0x8, + Kind_Argument = 1 << FirstKind, + Kind_Class = 2 << FirstKind | Kind_Scope, + Kind_Enum = 3 << FirstKind, + Kind_Enumerator = 4 << FirstKind, + Kind_File = 5 << FirstKind | Kind_Namespace, + Kind_TemplateParameter = 7 << FirstKind, + Kind_TypeDef = 8 << FirstKind, + Kind_TemplateTypeAlias = 9 << FirstKind, + Kind_Variable = 10 << FirstKind | Kind_Member + }; + +public: + virtual ~_CodeModelItem(); + + int kind() const; + + QStringList qualifiedName() const; + + QString name() const; + void setName(const QString &name); + + QStringList scope() const; + void setScope(const QStringList &scope); + + QString fileName() const; + void setFileName(const QString &fileName); + + FileModelItem file() const; + + void getStartPosition(int *line, int *column); + int startLine() const { return m_startLine; } + void setStartPosition(int line, int column); + + void getEndPosition(int *line, int *column); + void setEndPosition(int line, int column); + + SourceLocation sourceLocation() const; + + inline CodeModel *model() const { return m_model; } + + const _ScopeModelItem *enclosingScope() const; + void setEnclosingScope(const _ScopeModelItem *s); + +#ifndef QT_NO_DEBUG_STREAM + static void formatKind(QDebug &d, int k); + virtual void formatDebug(QDebug &d) const; +#endif + +protected: + explicit _CodeModelItem(CodeModel *model, int kind); + explicit _CodeModelItem(CodeModel *model, const QString &name, int kind); + +private: + CodeModel *m_model; + const _ScopeModelItem *m_enclosingScope = nullptr; + int m_kind; + int m_startLine; + int m_startColumn; + int m_endLine; + int m_endColumn; + QString m_name; + QString m_fileName; + QStringList m_scope; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const _CodeModelItem *t); +#endif + +class _ScopeModelItem: public _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_ScopeModelItem) + DECLARE_MODEL_NODE(Scope) + + ~_ScopeModelItem(); + + ClassList classes() const { return m_classes; } + const EnumList &enums() const { return m_enums; } + inline const FunctionList &functions() const { return m_functions; } + TypeDefList typeDefs() const { return m_typeDefs; } + TemplateTypeAliasList templateTypeAliases() const { return m_templateTypeAliases; } + VariableList variables() const { return m_variables; } + + void addClass(const ClassModelItem &item); + void addEnum(const EnumModelItem &item); + void addFunction(const FunctionModelItem &item); + void addTypeDef(const TypeDefModelItem &item); + void addTemplateTypeAlias(const TemplateTypeAliasModelItem &item); + void addVariable(const VariableModelItem &item); + + ClassModelItem findClass(const QString &name) const; + EnumModelItem findEnum(QAnyStringView name) const; + + struct FindEnumByValueReturn + { + operator bool() const { return bool(item); } + + EnumModelItem item; + QString qualifiedName; + }; + FindEnumByValueReturn findEnumByValue(QStringView value) const; + + FunctionList findFunctions(QAnyStringView name) const; + TypeDefModelItem findTypeDef(QAnyStringView name) const; + TemplateTypeAliasModelItem findTemplateTypeAlias(QAnyStringView name) const; + VariableModelItem findVariable(QAnyStringView name) const; + + void addEnumsDeclaration(const QString &enumsDeclaration); + QStringList enumsDeclarations() const { return m_enumsDeclarations; } + + FunctionModelItem declaredFunction(const FunctionModelItem &item); + + bool isEmpty() const; + void purgeClassDeclarations(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +protected: + explicit _ScopeModelItem(CodeModel *model, int kind = __node_kind); + explicit _ScopeModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + + void appendScope(const _ScopeModelItem &other); + +#ifndef QT_NO_DEBUG_STREAM + void formatScopeItemsDebug(QDebug &d) const; +#endif + +private: + qsizetype indexOfEnum(const QString &name) const; + + FindEnumByValueReturn findEnumByValueHelper(QStringView fullValue, + QStringView value) const; + static FindEnumByValueReturn + findEnumByValueRecursion(const _ScopeModelItem *scope, + QStringView fullValue, QStringView value, + bool searchSiblingNamespaces = true); + + ClassList m_classes; + EnumList m_enums; + TypeDefList m_typeDefs; + TemplateTypeAliasList m_templateTypeAliases; + VariableList m_variables; + FunctionList m_functions; + +private: + QStringList m_enumsDeclarations; +}; + +class _ClassModelItem: public _ScopeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_ClassModelItem) + DECLARE_MODEL_NODE(Class) + + struct BaseClass + { + QString name; + ClassModelItem klass; // Might be null in case of templates + Access accessPolicy = Access::Public; + }; + + struct UsingMember // Introducing a base class member via 'using' directive + { + QString className; + QString memberName; + Access access = Access::Public; + }; + + explicit _ClassModelItem(CodeModel *model, int kind = __node_kind); + explicit _ClassModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_ClassModelItem(); + + const QList<BaseClass> &baseClasses() const { return m_baseClasses; } + + const QList<UsingMember> &usingMembers() const; + void addUsingMember(const QString &className, const QString &memberName, + Access accessPolicy); + + void addBaseClass(const BaseClass &b) { m_baseClasses.append(b); } + + TemplateParameterList templateParameters() const; + void setTemplateParameters(const TemplateParameterList &templateParameters); + + bool extendsClass(const QString &name) const; + + void setClassType(CodeModel::ClassType type); + CodeModel::ClassType classType() const; + + void addPropertyDeclaration(const QString &propertyDeclaration); + QStringList propertyDeclarations() const { return m_propertyDeclarations; } + + bool isFinal() const { return m_final; } + void setFinal(bool f) { m_final = f; } + + bool isEmpty() const; + bool isTemplate() const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + QList<BaseClass> m_baseClasses; + QList<UsingMember> m_usingMembers; + TemplateParameterList m_templateParameters; + CodeModel::ClassType m_classType = CodeModel::Class; + + QStringList m_propertyDeclarations; + bool m_final = false; +}; + +class _NamespaceModelItem: public _ScopeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_NamespaceModelItem) + DECLARE_MODEL_NODE(Namespace) + + explicit _NamespaceModelItem(CodeModel *model, int kind = __node_kind); + explicit _NamespaceModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_NamespaceModelItem(); + + const NamespaceList &namespaces() const { return m_namespaces; } + + NamespaceType type() const { return m_type; } + void setType(NamespaceType t) { m_type = t; } + + void addNamespace(NamespaceModelItem item); + + NamespaceModelItem findNamespace(QAnyStringView name) const; + + void appendNamespace(const _NamespaceModelItem &other); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + NamespaceList m_namespaces; + NamespaceType m_type = NamespaceType::Default; +}; + +class _FileModelItem: public _NamespaceModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_FileModelItem) + DECLARE_MODEL_NODE(File) + + using _NamespaceModelItem::_NamespaceModelItem; + + ~_FileModelItem(); +}; + +class _ArgumentModelItem: public _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_ArgumentModelItem) + DECLARE_MODEL_NODE(Argument) + + explicit _ArgumentModelItem(CodeModel *model, int kind = __node_kind); + explicit _ArgumentModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_ArgumentModelItem(); + + TypeInfo type() const; + void setType(const TypeInfo &type); + + bool defaultValue() const; + void setDefaultValue(bool defaultValue); + + QString defaultValueExpression() const { return m_defaultValueExpression; } + void setDefaultValueExpression(const QString &expr) { m_defaultValueExpression = expr; } + + // Argument type has scope resolution "::ArgumentType" + bool scopeResolution() const; + void setScopeResolution(bool v); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + TypeInfo m_type; + QString m_defaultValueExpression; + bool m_defaultValue = false; + bool m_scopeResolution = false; +}; + +class _MemberModelItem: public _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_MemberModelItem) + DECLARE_MODEL_NODE(Member) + + explicit _MemberModelItem(CodeModel *model, int kind = __node_kind); + explicit _MemberModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_MemberModelItem(); + + bool isConstant() const; + void setConstant(bool isConstant); + + bool isVolatile() const; + void setVolatile(bool isVolatile); + + bool isStatic() const; + void setStatic(bool isStatic); + + bool isAuto() const; + void setAuto(bool isAuto); + + bool isFriend() const; + void setFriend(bool isFriend); + + bool isRegister() const; + void setRegister(bool isRegister); + + bool isExtern() const; + void setExtern(bool isExtern); + + bool isMutable() const; + void setMutable(bool isMutable); + + Access accessPolicy() const; + void setAccessPolicy(Access accessPolicy); + + TemplateParameterList templateParameters() const { return m_templateParameters; } + void setTemplateParameters(const TemplateParameterList &templateParameters) { m_templateParameters = templateParameters; } + + TypeInfo type() const; + void setType(const TypeInfo &type); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + TemplateParameterList m_templateParameters; + TypeInfo m_type; + Access m_accessPolicy = Access::Public; + union { + struct { + uint m_isConstant: 1; + uint m_isVolatile: 1; + uint m_isStatic: 1; + uint m_isAuto: 1; + uint m_isFriend: 1; + uint m_isRegister: 1; + uint m_isExtern: 1; + uint m_isMutable: 1; + }; + uint m_flags; + }; + +}; + +class _FunctionModelItem: public _MemberModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_FunctionModelItem) + DECLARE_MODEL_NODE(Function) + + explicit _FunctionModelItem(CodeModel *model, int kind = __node_kind); + explicit _FunctionModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_FunctionModelItem(); + + ArgumentList arguments() const; + + void addArgument(const ArgumentModelItem& item); + + CodeModel::FunctionType functionType() const; + void setFunctionType(CodeModel::FunctionType functionType); + + static std::optional<CodeModel::FunctionType> functionTypeFromName(QStringView name); + + FunctionAttributes attributes() const { return m_attributes; } + void setAttributes(FunctionAttributes a) { m_attributes = a; } + void setAttribute(FunctionAttribute a, bool on = true) { m_attributes.setFlag(a, on); } + + bool isDeleted() const; + void setDeleted(bool d); + + bool isInline() const; + void setInline(bool isInline); + + bool isHiddenFriend() const; + void setHiddenFriend(bool f); + + bool isVariadics() const; + void setVariadics(bool isVariadics); + + bool scopeResolution() const; // Return type has scope resolution "::ReturnType" + void setScopeResolution(bool v); + + bool isDefaultConstructor() const; + bool isSpaceshipOperator() const; + + bool isSimilar(const FunctionModelItem &other) const; + + bool isNoExcept() const; + + bool isOperator() const; + + ExceptionSpecification exceptionSpecification() const; + void setExceptionSpecification(ExceptionSpecification e); + + QString typeSystemSignature() const; // For dumping out type system files + + // Private, for usage by the clang builder. + void _determineType(); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + CodeModel::FunctionType _determineTypeHelper() const; + + ArgumentList m_arguments; + FunctionAttributes m_attributes; + CodeModel::FunctionType m_functionType = CodeModel::Normal; + union { + struct { + uint m_isDeleted: 1; + uint m_isInline: 1; + uint m_isVariadics: 1; + uint m_isHiddenFriend: 1; + uint m_isInvokable : 1; // Qt + uint m_scopeResolution: 1; + }; + uint m_flags; + }; + ExceptionSpecification m_exceptionSpecification = ExceptionSpecification::Unknown; +}; + +class _VariableModelItem: public _MemberModelItem +{ +public: + DECLARE_MODEL_NODE(Variable) + + using _MemberModelItem::_MemberModelItem; +}; + +class _TypeDefModelItem: public _CodeModelItem +{ +public: + DECLARE_MODEL_NODE(TypeDef) + + explicit _TypeDefModelItem(CodeModel *model, int kind = __node_kind); + explicit _TypeDefModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + + TypeInfo type() const; + void setType(const TypeInfo &type); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + TypeInfo m_type; +}; + +class _TemplateTypeAliasModelItem : public _CodeModelItem +{ +public: + DECLARE_MODEL_NODE(TemplateTypeAlias) + + explicit _TemplateTypeAliasModelItem(CodeModel *model, int kind = __node_kind); + explicit _TemplateTypeAliasModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + + TemplateParameterList templateParameters() const; + void addTemplateParameter(const TemplateParameterModelItem &templateParameter); + + TypeInfo type() const; + void setType(const TypeInfo &type); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + TemplateParameterList m_templateParameters; + TypeInfo m_type; +}; + +class _EnumModelItem: public _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_EnumModelItem) + DECLARE_MODEL_NODE(Enum) + + explicit _EnumModelItem(CodeModel *model, const QString &name, int kind = __node_kind); + explicit _EnumModelItem(CodeModel *model, int kind = __node_kind); + ~_EnumModelItem(); + + Access accessPolicy() const; + void setAccessPolicy(Access accessPolicy); + + bool hasValues() const { return !m_enumerators.isEmpty(); } + EnumeratorList enumerators() const; + void addEnumerator(const EnumeratorModelItem &item); + + EnumKind enumKind() const { return m_enumKind; } + void setEnumKind(EnumKind kind) { m_enumKind = kind; } + + qsizetype indexOfValue(QStringView value) const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + + bool isDeprecated() const; + void setDeprecated(bool d); + + bool isSigned() const; + void setSigned(bool s); + + QString underlyingType() const; + void setUnderlyingType(const QString &underlyingType); + +private: + QString m_underlyingType; + Access m_accessPolicy = Access::Public; + EnumeratorList m_enumerators; + EnumKind m_enumKind = CEnum; + bool m_deprecated = false; + bool m_signed = true; +}; + +class _EnumeratorModelItem: public _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_EnumeratorModelItem) + DECLARE_MODEL_NODE(Enumerator) + + explicit _EnumeratorModelItem(CodeModel *model, int kind = __node_kind); + explicit _EnumeratorModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_EnumeratorModelItem(); + + QString stringValue() const; + void setStringValue(const QString &stringValue); + + EnumValue value() const { return m_value; } + void setValue(EnumValue v) { m_value = v; } + + bool isDeprecated() const; + void setDeprecated(bool d); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + QString m_stringValue; + EnumValue m_value; + bool m_deprecated = false; +}; + +class _TemplateParameterModelItem: public _CodeModelItem +{ +public: + Q_DISABLE_COPY_MOVE(_TemplateParameterModelItem) + DECLARE_MODEL_NODE(TemplateParameter) + + explicit _TemplateParameterModelItem(CodeModel *model, int kind = __node_kind); + explicit _TemplateParameterModelItem(CodeModel *model, const QString &name, + int kind = __node_kind); + ~_TemplateParameterModelItem(); + + TypeInfo type() const; + void setType(const TypeInfo &type); + + bool defaultValue() const; + void setDefaultValue(bool defaultValue); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +private: + TypeInfo m_type; + bool m_defaultValue = false; +}; + +#endif // CODEMODEL_H diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h b/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h new file mode 100644 index 000000000..e5c429bd0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/codemodel_enums.h @@ -0,0 +1,61 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CODEMODEL_ENUMS_H +#define CODEMODEL_ENUMS_H + +#include <QtCore/qflags.h> + +enum ReferenceType { + NoReference, + LValueReference, + RValueReference +}; + +enum EnumKind { + CEnum, // Standard C: enum Foo { value1, value2 } + AnonymousEnum, // enum { value1, value2 } + EnumClass // C++ 11 : enum class Foo { value1, value2 } +}; + +enum class Indirection +{ + Pointer, // int * + ConstPointer // int *const +}; + +enum class ExceptionSpecification +{ + Unknown, + NoExcept, + Throws +}; + +enum class NamespaceType +{ + Default, + Anonymous, + Inline +}; + +enum class Access +{ + Private, + Protected, + Public +}; + +enum class FunctionAttribute { + Abstract = 0x00000001, + Static = 0x00000002, + Virtual = 0x00000004, + Override = 0x00000008, + Final = 0x00000010, + Deprecated = 0x00000020, // Code annotation + Explicit = 0x00000040, // Constructor +}; + +Q_DECLARE_FLAGS(FunctionAttributes, FunctionAttribute) +Q_DECLARE_OPERATORS_FOR_FLAGS(FunctionAttributes) + +#endif // CODEMODEL_ENUMS_H diff --git a/sources/shiboken6/ApiExtractor/parser/codemodel_fwd.h b/sources/shiboken6/ApiExtractor/parser/codemodel_fwd.h new file mode 100644 index 000000000..f0a25c9db --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/codemodel_fwd.h @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2002-2005 Roberto Raggi <roberto@kdevelop.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#ifndef CODEMODEL_FWD_H +#define CODEMODEL_FWD_H + +#include <QtCore/QList> + +#include <memory> + +// forward declarations +class CodeModel; +class _ArgumentModelItem; +class _ClassModelItem; +class _CodeModelItem; +class _EnumModelItem; +class _EnumeratorModelItem; +class _FileModelItem; +class _FunctionModelItem; +class _NamespaceModelItem; +class _ScopeModelItem; +class _TemplateParameterModelItem; +class _TypeDefModelItem; +class _TemplateTypeAliasModelItem; +class _VariableModelItem; +class _MemberModelItem; +class TypeInfo; + +using ArgumentModelItem = std::shared_ptr<_ArgumentModelItem>; +using ClassModelItem = std::shared_ptr<_ClassModelItem>; +using CodeModelItem = std::shared_ptr<_CodeModelItem>; +using EnumModelItem = std::shared_ptr<_EnumModelItem>; +using EnumeratorModelItem = std::shared_ptr<_EnumeratorModelItem>; +using FileModelItem = std::shared_ptr<_FileModelItem>; +using FunctionModelItem = std::shared_ptr<_FunctionModelItem>; +using NamespaceModelItem = std::shared_ptr<_NamespaceModelItem>; +using ScopeModelItem = std::shared_ptr<_ScopeModelItem>; +using TemplateParameterModelItem = std::shared_ptr<_TemplateParameterModelItem>; +using TypeDefModelItem = std::shared_ptr<_TypeDefModelItem>; +using TemplateTypeAliasModelItem = std::shared_ptr<_TemplateTypeAliasModelItem>; +using VariableModelItem = std::shared_ptr<_VariableModelItem>; +using MemberModelItem = std::shared_ptr<_MemberModelItem>; + +using ArgumentList = QList<ArgumentModelItem>; +using ClassList = QList<ClassModelItem>; +using ItemList = QList<CodeModelItem>; +using EnumList = QList<EnumModelItem>; +using EnumeratorList = QList<EnumeratorModelItem>; +using FileList = QList<FileModelItem>; +using FunctionList = QList<FunctionModelItem>; +using NamespaceList = QList<NamespaceModelItem>; +using ScopeList = QList<ScopeModelItem>; +using TemplateParameterList = QList<TemplateParameterModelItem>; +using TypeDefList = QList<TypeDefModelItem>; +using TemplateTypeAliasList = QList<TemplateTypeAliasModelItem>; +using VariableList = QList<VariableModelItem>; +using MemberList = QList<MemberModelItem>; + +#endif // CODEMODEL_FWD_H diff --git a/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp b/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp new file mode 100644 index 000000000..3749e16a8 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/enumvalue.cpp @@ -0,0 +1,103 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "enumvalue.h" + +#include <QtCore/QDebug> +#include <QtCore/QString> +#include <QtCore/QTextStream> + +using namespace Qt::StringLiterals; + +QString EnumValue::toString() const +{ + return m_type == EnumValue::Signed + ? QString::number(m_value) : QString::number(m_unsignedValue); +} + +QString EnumValue::toHex(int fieldWidth) const +{ + QString result; + QTextStream str(&result); + // Note: Qt goofes up formatting of negative padded hex numbers, it ends up + // with "0x00-1". Write '-' before. + if (isNegative()) + str << '-'; + str << "0x" << Qt::hex; + if (fieldWidth) { + str.setFieldWidth(fieldWidth); + str.setPadChar(u'0'); + } + if (m_type == EnumValue::Signed) + str << qAbs(m_value); + else + str << m_unsignedValue; + return result; +} + +void EnumValue::setValue(qint64 v) +{ + m_value = v; + m_type = Signed; +} + +void EnumValue::setUnsignedValue(quint64 v) +{ + m_unsignedValue = v; + m_type = Unsigned; +} + +EnumValue EnumValue::toUnsigned() const +{ + if (m_type == Unsigned) + return *this; + EnumValue result; + result.setUnsignedValue(m_value < 0 ? quint64(-m_value) : quint64(m_value)); + return result; +} + +bool comparesEqual(const EnumValue &lhs, const EnumValue &rhs) noexcept +{ + if (lhs.m_type != rhs.m_type) + return false; + return lhs.m_type == EnumValue::Signed + ? lhs.m_value == rhs.m_value : lhs.m_unsignedValue == rhs.m_unsignedValue; +} + +void EnumValue::formatDebugHex(QDebug &d) const +{ + d << "0x" << Qt::hex; + formatDebug(d); + d << Qt::dec; +} + +void EnumValue::formatDebug(QDebug &d) const +{ + + if (m_type == EnumValue::Signed) + d << m_value; + else + d << m_unsignedValue << 'u'; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d,const EnumValue &v) +{ + QDebugStateSaver saver(d); + d.nospace(); + d.noquote(); + d << "EnumValue("; + v.formatDebug(d); + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +QTextStream &operator<<(QTextStream &s, const EnumValue &v) +{ + if (v.m_type == EnumValue::Signed) + s << v.m_value; + else + s << v.m_unsignedValue; + return s; +} diff --git a/sources/shiboken6/ApiExtractor/parser/enumvalue.h b/sources/shiboken6/ApiExtractor/parser/enumvalue.h new file mode 100644 index 000000000..bbd5a712d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/enumvalue.h @@ -0,0 +1,61 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ENUMVALUE_H +#define ENUMVALUE_H + +#include <QtCore/qtypes.h> +#include <QtCore/qtclasshelpermacros.h> +#include <QtCore/QtCompare> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QString) +QT_FORWARD_DECLARE_CLASS(QTextStream) + +class EnumValue +{ +public: + enum Type + { + Signed, + Unsigned + }; + + QString toString() const; + QString toHex(int fieldWidth = 0) const; + + Type type() { return m_type; } + qint64 value() const { return m_value; } + quint64 unsignedValue() const { return m_unsignedValue; } + bool isNullValue() const { return m_type == Signed ? m_value == 0 : m_unsignedValue == 0u; } + bool isNegative() const { return m_type == Signed && m_value < 0; } + + void setValue(qint64 v); + void setUnsignedValue(quint64 v); + + EnumValue toUnsigned() const; + + bool equals(const EnumValue &rhs) const; + + void formatDebug(QDebug &d) const; + void formatDebugHex(QDebug &d) const; + +private: + friend bool comparesEqual(const EnumValue &lhs, + const EnumValue &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(EnumValue) + +#ifndef QT_NO_DEBUG_STREAM + friend QDebug operator<<(QDebug, const EnumValue &); +#endif + friend QTextStream &operator<<(QTextStream &, const EnumValue &); + + union + { + qint64 m_value = 0; + quint64 m_unsignedValue; + }; + Type m_type = Signed; +}; + +#endif // ENUMVALUE_H diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp new file mode 100644 index 000000000..f8c5c31d8 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.cpp @@ -0,0 +1,624 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2002-2005 Roberto Raggi <roberto@kdevelop.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "typeinfo.h" +#include "codemodel.h" + +#include <clangparser/clangutils.h> +#include <debughelpers_p.h> + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QStack> +#include <QtCore/QTextStream> + +#include <iostream> + +using namespace Qt::StringLiterals; + +class TypeInfoData : public QSharedData +{ + +public: + TypeInfoData(); + + bool isVoid() const; + bool equals(const TypeInfoData &other) const; + bool isStdType() const; + void simplifyStdType(); + + QStringList m_qualifiedName; + QStringList m_arrayElements; + TypeInfo::TypeInfoList m_arguments; + TypeInfo::TypeInfoList m_instantiations; + TypeInfo::Indirections m_indirections; + + union { + uint flags; + + struct { + uint m_constant: 1; + uint m_volatile: 1; + uint m_functionPointer: 1; + uint m_padding: 29; + }; + }; + + ReferenceType m_referenceType = NoReference; +}; + +TypeInfoData::TypeInfoData() : flags(0) +{ +} + +TypeInfo::TypeInfo() : d(new TypeInfoData) +{ +} + +TypeInfo::~TypeInfo() = default; +TypeInfo::TypeInfo(const TypeInfo &) = default; +TypeInfo& TypeInfo::operator=(const TypeInfo &) = default; +TypeInfo::TypeInfo(TypeInfo &&) noexcept = default; +TypeInfo &TypeInfo::operator=(TypeInfo &&) noexcept = default; + +static inline TypeInfo createType(const QString &name) +{ + TypeInfo result; + result.addName(name); + return result; +} + +TypeInfo TypeInfo::voidType() +{ + static const TypeInfo result = createType(u"void"_s); + return result; +} + +TypeInfo TypeInfo::varArgsType() +{ + static const TypeInfo result = createType(u"..."_s); + return result; +} + +TypeInfo TypeInfo::combine(const TypeInfo &__lhs, const TypeInfo &__rhs) +{ + TypeInfo __result = __lhs; + + __result.setConstant(__result.isConstant() || __rhs.isConstant()); + __result.setVolatile(__result.isVolatile() || __rhs.isVolatile()); + if (__rhs.referenceType() > __result.referenceType()) + __result.setReferenceType(__rhs.referenceType()); + + const auto indirections = __rhs.indirectionsV(); + for (auto i : indirections) + __result.addIndirection(i); + + __result.setArrayElements(__result.arrayElements() + __rhs.arrayElements()); + + const auto &instantiations = __rhs.instantiations(); + for (const auto &i : instantiations) + __result.addInstantiation(i); + + return __result; +} + +QStringList TypeInfo::qualifiedName() const +{ + return d->m_qualifiedName; +} + +void TypeInfo::setQualifiedName(const QStringList &qualified_name) +{ + if (d->m_qualifiedName != qualified_name) + d->m_qualifiedName = qualified_name; +} + +void TypeInfo::addName(const QString &n) +{ + d->m_qualifiedName.append(n); +} + +bool TypeInfoData::isVoid() const +{ + return m_indirections.isEmpty() && m_referenceType == NoReference + && m_arguments.isEmpty() && m_arrayElements.isEmpty() + && m_instantiations.isEmpty() + && m_qualifiedName.size() == 1 + && m_qualifiedName.constFirst() == u"void"; +} + +bool TypeInfo::isVoid() const +{ + return d->isVoid(); +} + +bool TypeInfo::isConstant() const +{ + return d->m_constant; +} + +void TypeInfo::setConstant(bool is) +{ + if (d->m_constant != is) + d->m_constant = is; +} + +bool TypeInfo::isVolatile() const +{ + return d->m_volatile; +} + +void TypeInfo::setVolatile(bool is) +{ + if (d->m_volatile != is) + d->m_volatile = is; +} + +ReferenceType TypeInfo::referenceType() const +{ + return d->m_referenceType; +} + +void TypeInfo::setReferenceType(ReferenceType r) +{ + if (d->m_referenceType != r) + d->m_referenceType = r; +} + +const TypeInfo::Indirections &TypeInfo::indirectionsV() const +{ + return d->m_indirections; +} + +void TypeInfo::setIndirectionsV(const TypeInfo::Indirections &i) +{ + if (d->m_indirections != i) + d->m_indirections = i; +} + +int TypeInfo::indirections() const +{ + return d->m_indirections.size(); +} + +void TypeInfo::setIndirections(int indirections) +{ + const Indirections newValue(indirections, Indirection::Pointer); + if (d->m_indirections != newValue) + d->m_indirections = newValue; +} + +void TypeInfo::addIndirection(Indirection i) +{ + d->m_indirections.append(i); +} + +bool TypeInfo::isFunctionPointer() const +{ + return d->m_functionPointer; +} + +void TypeInfo::setFunctionPointer(bool is) +{ + if (d->m_functionPointer != is) + d->m_functionPointer = is; +} + +const QStringList &TypeInfo::arrayElements() const +{ + return d->m_arrayElements; +} + +void TypeInfo::setArrayElements(const QStringList &arrayElements) +{ + if (d->m_arrayElements != arrayElements) + d->m_arrayElements = arrayElements; +} + +void TypeInfo::addArrayElement(const QString &a) +{ + d->m_arrayElements.append(a); +} + +const QList<TypeInfo> &TypeInfo::arguments() const +{ + return d->m_arguments; +} + +void TypeInfo::setArguments(const QList<TypeInfo> &arguments) +{ + if (d->m_arguments != arguments) + d->m_arguments = arguments; +} + +void TypeInfo::addArgument(const TypeInfo &arg) +{ + d->m_arguments.append(arg); +} + +const TypeInfo::TypeInfoList &TypeInfo::instantiations() const +{ + return d->m_instantiations; +} + +TypeInfo::TypeInfoList &TypeInfo::instantiations() +{ + return d->m_instantiations; +} + +void TypeInfo::setInstantiations(const TypeInfoList &i) +{ + if (d->m_instantiations != i) + d->m_instantiations = i; +} + +void TypeInfo::addInstantiation(const TypeInfo &i) +{ + d->m_instantiations.append(i); +} + +void TypeInfo::clearInstantiations() +{ + if (!d->m_instantiations.isEmpty()) + d->m_instantiations.clear(); +} + +bool TypeInfo::isPlain() const +{ + return d->m_constant == 0 && d->m_volatile == 0 && d->m_referenceType == NoReference + && d->m_indirections.isEmpty() && d->m_arrayElements.isEmpty(); +} + +TypeInfo TypeInfo::resolveType(TypeInfo const &__type, const ScopeModelItem &__scope) +{ + CodeModel *__model = __scope->model(); + Q_ASSERT(__model != nullptr); + + return TypeInfo::resolveType(__model->findItem(__type.qualifiedName(), __scope), __type, __scope); +} + +TypeInfo TypeInfo::resolveType(CodeModelItem __item, TypeInfo const &__type, const ScopeModelItem &__scope) +{ + // Copy the type and replace with the proper qualified name. This + // only makes sence to do if we're actually getting a resolved + // type with a namespace. We only get this if the returned type + // has more than 2 entries in the qualified name... This test + // could be improved by returning if the type was found or not. + TypeInfo otherType(__type); + if (__item && __item->qualifiedName().size() > 1) { + otherType.setQualifiedName(__item->qualifiedName()); + } + + if (TypeDefModelItem __typedef = std::dynamic_pointer_cast<_TypeDefModelItem>(__item)) { + const TypeInfo combined = TypeInfo::combine(__typedef->type(), otherType); + const CodeModelItem nextItem = __scope->model()->findItem(combined.qualifiedName(), __scope); + if (!nextItem) + return combined; + // PYSIDE-362, prevent recursion on opaque structs like + // typedef struct xcb_connection_t xcb_connection_t; + if (nextItem.get() ==__item.get()) { + std::cerr << "** WARNING Bailing out recursion of " << __FUNCTION__ + << "() on " << qPrintable(__type.qualifiedName().join(u"::"_s)) + << std::endl; + return otherType; + } + return resolveType(nextItem, combined, __scope); + } + + if (TemplateTypeAliasModelItem templateTypeAlias = std::dynamic_pointer_cast<_TemplateTypeAliasModelItem>(__item)) { + + TypeInfo combined = TypeInfo::combine(templateTypeAlias->type(), otherType); + // For the alias "template<typename T> using QList = QVector<T>" with + // other="QList<int>", replace the instantiations to obtain "QVector<int>". + auto aliasInstantiations = templateTypeAlias->type().instantiations(); + const auto &concreteInstantiations = otherType.instantiations(); + const auto count = qMin(aliasInstantiations.size(), concreteInstantiations.size()); + for (qsizetype i = 0; i < count; ++i) + aliasInstantiations[i] = concreteInstantiations.at(i); + combined.setInstantiations(aliasInstantiations); + const CodeModelItem nextItem = CodeModel::findItem(combined.qualifiedName(), __scope); + if (!nextItem) + return combined; + return resolveType(nextItem, combined, __scope); + } + + return otherType; +} + +// Handler for clang::parseTemplateArgumentList() that populates +// TypeInfo::m_instantiations +class TypeInfoTemplateArgumentHandler +{ +public: + explicit TypeInfoTemplateArgumentHandler(TypeInfo *t) + { + m_parseStack.append(t); + } + + void operator()(int level, QStringView name) + { + if (level > m_parseStack.size()) { + Q_ASSERT(!top()->instantiations().isEmpty()); + m_parseStack.push(&top()->instantiations().back()); + } + while (level < m_parseStack.size()) + m_parseStack.pop(); + TypeInfo instantiation; + if (name.startsWith(u"const ")) { + instantiation.setConstant(true); + name = name.mid(6); + } + instantiation.setQualifiedName(qualifiedName(name)); + top()->addInstantiation(instantiation); + } + +private: + TypeInfo *top() const { return m_parseStack.back(); } + + static QStringList qualifiedName(QStringView name) + { + QStringList result; + const auto nameParts = name.split(u"::"); + result.reserve(nameParts.size()); + for (const auto &p : nameParts) + result.append(p.toString()); + return result; + } + + QStack<TypeInfo *> m_parseStack; +}; + +std::pair<qsizetype, qsizetype> + TypeInfo::parseTemplateArgumentList(const QString &l, qsizetype from) +{ + return clang::parseTemplateArgumentList(l, clang::TemplateArgumentHandler(TypeInfoTemplateArgumentHandler(this)), from); +} + +QString TypeInfo::toString() const +{ + QString tmp; + if (isConstant()) + tmp += u"const "_s; + + if (isVolatile()) + tmp += u"volatile "_s; + + tmp += d->m_qualifiedName.join(u"::"_s); + + if (const auto instantiationCount = d->m_instantiations.size()) { + tmp += u'<'; + for (qsizetype i = 0; i < instantiationCount; ++i) { + if (i) + tmp += u", "_s; + tmp += d->m_instantiations.at(i).toString(); + } + if (tmp.endsWith(u'>')) + tmp += u' '; + tmp += u'>'; + } + + for (Indirection i : d->m_indirections) + tmp.append(indirectionKeyword(i)); + + switch (referenceType()) { + case NoReference: + break; + case LValueReference: + tmp += u'&'; + break; + case RValueReference: + tmp += u"&&"_s; + break; + } + + if (isFunctionPointer()) { + tmp += u" (*)("_s; + for (qsizetype i = 0; i < d->m_arguments.size(); ++i) { + if (i != 0) + tmp += u", "_s; + + tmp += d->m_arguments.at(i).toString(); + } + tmp += u')'; + } + + for (const QString &elt : d->m_arrayElements) + tmp += u'[' + elt + u']'; + + return tmp; +} + +bool TypeInfoData::equals(const TypeInfoData &other) const +{ + if (m_arrayElements.size() != other.m_arrayElements.size()) + return false; + +#if defined (RXX_CHECK_ARRAY_ELEMENTS) // ### it'll break + for (qsizetype i = 0; i < arrayElements().size(); ++i) { + QString elt1 = arrayElements().at(i).trimmed(); + QString elt2 = other.arrayElements().at(i).trimmed(); + + if (elt1 != elt2) + return false; + } +#endif + + return flags == other.flags + && m_qualifiedName == other.m_qualifiedName + && (!m_functionPointer || m_arguments == other.m_arguments) + && m_instantiations == other.m_instantiations; +} + + +bool comparesEqual(const TypeInfo &lhs, const TypeInfo &rhs) noexcept +{ + return lhs.d.data() == rhs.d.data() || lhs.d->equals(*rhs.d); +} + +QString TypeInfo::indirectionKeyword(Indirection i) +{ + return i == Indirection::Pointer ? "*"_L1 : "*const"_L1; +} + +bool TypeInfo::stripLeadingConst(QString *s) +{ + return stripLeadingQualifier("const"_L1, s); +} + +bool TypeInfo::stripLeadingVolatile(QString *s) +{ + return stripLeadingQualifier("volatile"_L1, s); +} + +bool TypeInfo::stripLeadingQualifier(QLatin1StringView qualifier, QString *s) +{ + // "const int x" + const auto qualifierSize = qualifier.size(); + if (s->size() < qualifierSize + 1 || !s->startsWith(qualifier) + || !s->at(qualifierSize).isSpace()) { + return false; + } + s->remove(0, qualifierSize + 1); + while (!s->isEmpty() && s->at(0).isSpace()) + s->remove(0, 1); + return true; +} + +// Strip all const/volatile/*/& +void TypeInfo::stripQualifiers(QString *s) +{ + stripLeadingConst(s); + stripLeadingVolatile(s); + while (s->endsWith(u'&') || s->endsWith(u'*') || s->endsWith(u' ')) + s->chop(1); +} + +// Helper functionality to simplify a raw standard type as returned by +// clang_getCanonicalType() for g++ standard containers from +// "std::__cxx11::list<int, std::allocator<int> >" or +// "std::__1::list<int, std::allocator<int> >" -> "std::list<int>". + +bool TypeInfoData::isStdType() const +{ + return m_qualifiedName.size() > 1 + && m_qualifiedName.constFirst() == u"std"; +} + +bool TypeInfo::isStdType() const +{ + return d->isStdType(); +} + +static inline bool discardStdType(const QString &name) +{ + return name == u"allocator" || name == u"less"; +} + +void TypeInfoData::simplifyStdType() +{ + Q_ASSERT(isStdType()); + if (m_qualifiedName.at(1).startsWith(u"__")) + m_qualifiedName.removeAt(1); + for (auto t = m_instantiations.size() - 1; t >= 0; --t) { + if (m_instantiations.at(t).isStdType()) { + if (discardStdType(m_instantiations.at(t).qualifiedName().constLast())) + m_instantiations.removeAt(t); + else + m_instantiations[t].simplifyStdType(); + } + } +} + +void TypeInfo::simplifyStdType() +{ + if (isStdType()) + d->simplifyStdType(); +} + +void TypeInfo::formatTypeSystemSignature(QTextStream &str) const +{ + if (d->m_constant) + str << "const "; + str << d->m_qualifiedName.join(u"::"_s); + switch (d->m_referenceType) { + case NoReference: + break; + case LValueReference: + str << '&'; + break; + case RValueReference: + str << "&&"; + break; + } + for (auto i : d->m_indirections) { + switch (i) { + case Indirection::Pointer: + str << '*'; + break; + case Indirection::ConstPointer: + str << "* const"; + break; + } + } +} + +#ifndef QT_NO_DEBUG_STREAM +void TypeInfo::formatDebug(QDebug &debug) const +{ + debug << '"'; + formatSequence(debug, d->m_qualifiedName.begin(), d->m_qualifiedName.end(), "\", \""); + debug << '"'; + if (d->m_constant) + debug << ", [const]"; + if (d->m_volatile) + debug << ", [volatile]"; + if (!d->m_indirections.isEmpty()) { + debug << ", indirections="; + for (auto i : d->m_indirections) + debug << ' ' << TypeInfo::indirectionKeyword(i); + } + switch (d->m_referenceType) { + case NoReference: + break; + case LValueReference: + debug << ", [ref]"; + break; + case RValueReference: + debug << ", [rvalref]"; + break; + } + if (!d->m_instantiations.isEmpty()) { + debug << ", template<"; + formatSequence(debug, d->m_instantiations.begin(), d->m_instantiations.end()); + debug << '>'; + } + if (d->m_functionPointer) { + debug << ", function ptr("; + formatSequence(debug, d->m_arguments.begin(), d->m_arguments.end()); + debug << ')'; + } + if (!d->m_arrayElements.isEmpty()) { + debug << ", array[" << d->m_arrayElements.size() << "]["; + formatSequence(debug, d->m_arrayElements.begin(), d->m_arrayElements.end()); + debug << ']'; + } +} + +QDebug operator<<(QDebug d, const TypeInfo &t) +{ + QDebugStateSaver s(d); + const int verbosity = d.verbosity(); + d.noquote(); + d.nospace(); + d << "TypeInfo("; + if (verbosity > 2) + t.formatDebug(d); + else + d << t.toString(); + d << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/parser/typeinfo.h b/sources/shiboken6/ApiExtractor/parser/typeinfo.h new file mode 100644 index 000000000..e4f363b67 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/parser/typeinfo.h @@ -0,0 +1,128 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2002-2005 Roberto Raggi <roberto@kdevelop.org> +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPEINFO_H +#define TYPEINFO_H + +#include "codemodel_enums.h" +#include "codemodel_fwd.h" + +#include <QtCore/QString> +#include <QtCore/QSharedDataPointer> +#include <QtCore/QtCompare> +#include <QtCore/QStringList> + +#include <utility> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QTextStream) + +class TypeInfoData; + +class TypeInfo +{ + friend class TypeParser; +public: + using Indirections = QList<Indirection>; + using TypeInfoList = QList<TypeInfo>; + + TypeInfo(); + ~TypeInfo(); + TypeInfo(const TypeInfo &); + TypeInfo& operator=(const TypeInfo &); + TypeInfo(TypeInfo &&) noexcept; + TypeInfo &operator=(TypeInfo &&) noexcept; + + static TypeInfo voidType(); + static TypeInfo varArgsType(); + + QStringList qualifiedName() const; + void setQualifiedName(const QStringList &qualified_name); + void addName(const QString &); + + bool isVoid() const; + + bool isConstant() const; + void setConstant(bool is); + + bool isVolatile() const; + + void setVolatile(bool is); + + ReferenceType referenceType() const; + void setReferenceType(ReferenceType r); + + const Indirections &indirectionsV() const; + void setIndirectionsV(const Indirections &i); + void addIndirection(Indirection i); + + // "Legacy", rename? + int indirections() const; + + void setIndirections(int indirections); + + bool isFunctionPointer() const; + void setFunctionPointer(bool is); + + const QStringList &arrayElements() const; + void setArrayElements(const QStringList &arrayElements); + + void addArrayElement(const QString &a); + + const TypeInfoList &arguments() const; + void setArguments(const TypeInfoList &arguments); + void addArgument(const TypeInfo &arg); + + const TypeInfoList &instantiations() const; + TypeInfoList &instantiations(); // for parsing only + void setInstantiations(const TypeInfoList &i); + void addInstantiation(const TypeInfo &i); + void clearInstantiations(); + + bool isPlain() const; // neither const,volatile, no indirections/references, array + + bool isStdType() const; + + std::pair<qsizetype, qsizetype> + parseTemplateArgumentList(const QString &l, qsizetype from = 0); + + // ### arrays and templates?? + + QString toString() const; + + static TypeInfo combine(const TypeInfo &__lhs, const TypeInfo &__rhs); + static TypeInfo resolveType(TypeInfo const &__type, const ScopeModelItem &__scope); + + void formatTypeSystemSignature(QTextStream &str) const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + + static QString indirectionKeyword(Indirection i); + + static bool stripLeadingConst(QString *s); + static bool stripLeadingVolatile(QString *s); + static bool stripLeadingQualifier(QLatin1StringView qualifier, QString *s); + static void stripQualifiers(QString *s); + + void simplifyStdType(); + +private: + friend bool comparesEqual(const TypeInfo &lhs, + const TypeInfo &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(TypeInfo) + + QSharedDataPointer<TypeInfoData> d; + + friend class TypeInfoTemplateArgumentHandler; + + static TypeInfo resolveType(CodeModelItem item, TypeInfo const &__type, const ScopeModelItem &__scope); +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const TypeInfo &t); +#endif + +#endif // TYPEINFO_H diff --git a/sources/shiboken6/ApiExtractor/predefined_templates.cpp b/sources/shiboken6/ApiExtractor/predefined_templates.cpp new file mode 100644 index 000000000..992f735ac --- /dev/null +++ b/sources/shiboken6/ApiExtractor/predefined_templates.cpp @@ -0,0 +1,276 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "predefined_templates.h" + +#include "qtcompat.h" + +using namespace Qt::StringLiterals; + +static QString pySequenceToCppContainer(const QString &insertFunc, + bool reserve) +{ + QString result = u"(%out).clear();\n"_s; + if (reserve) { + result += uR"(if (PyList_Check(%in)) { + const Py_ssize_t size = PySequence_Size(%in); + if (size > 10) + (%out).reserve(size); +} + +)"_s; + } + + result += uR"(Shiboken::AutoDecRef it(PyObject_GetIter(%in)); +while (true) { + Shiboken::AutoDecRef pyItem(PyIter_Next(it.object())); + if (pyItem.isNull()) { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + break; + } + %OUTTYPE_0 cppItem = %CONVERTTOCPP[%OUTTYPE_0](pyItem); + (%out).)"_s; + + result += insertFunc; + result += uR"((cppItem); +} +)"_s; + return result; +} + +// Convert a sequence to a limited/fixed array +static QString pySequenceToCppArray() +{ + return uR"(Shiboken::AutoDecRef it(PyObject_GetIter(%in)); +for (auto oit = std::begin(%out), oend = std::end(%out); oit != oend; ++oit) { + Shiboken::AutoDecRef pyItem(PyIter_Next(it.object())); + if (pyItem.isNull()) { + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + break; + } + %OUTTYPE_0 cppItem = %CONVERTTOCPP[%OUTTYPE_0](pyItem); + *oit = cppItem; +} +)"_s; +} + +static constexpr auto stlMapKeyAccessor = "->first"_L1; +static constexpr auto stlMapValueAccessor = "->second"_L1; +static constexpr auto qtMapKeyAccessor = ".key()"_L1; +static constexpr auto qtMapValueAccessor = ".value()"_L1; + +static QString cppMapToPyDict(bool isQMap) +{ + return uR"(PyObject *%out = PyDict_New(); +for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ++it) { + const auto &key = it)"_s + + (isQMap ? qtMapKeyAccessor : stlMapKeyAccessor) + + uR"(; + const auto &value = it)"_s + + (isQMap ? qtMapValueAccessor : stlMapValueAccessor) + + uR"(; + PyObject *pyKey = %CONVERTTOPYTHON[%INTYPE_0](key); + PyObject *pyValue = %CONVERTTOPYTHON[%INTYPE_1](value); + PyDict_SetItem(%out, pyKey, pyValue); + Py_DECREF(pyKey); + Py_DECREF(pyValue); +} +return %out; +)"_s; +} + +static QString pyDictToCppMap(bool isQMap) +{ + return uR"(PyObject *key; +PyObject *value; +%out.clear(); +Py_ssize_t pos = 0; +while (PyDict_Next(%in, &pos, &key, &value)) { + %OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key); + %OUTTYPE_1 cppValue = %CONVERTTOCPP[%OUTTYPE_1](value); + %out.insert()"_s + // STL needs a pair + + (isQMap ? u"cppKey, cppValue"_s : u"{cppKey, cppValue}"_s) + uR"(); +} +)"_s; +} + +// Convert a STL or Qt multi map to Dict of Lists using upperBound() +static QString cppMultiMapToPyDict(bool isQMultiMap) +{ + return uR"(PyObject *%out = PyDict_New(); + for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ) { + const auto &key = it)"_s + + (isQMultiMap ? qtMapKeyAccessor : stlMapKeyAccessor) + + uR"(; + PyObject *pyKey = %CONVERTTOPYTHON[%INTYPE_0](key); + auto upper = %in.)"_s + + (isQMultiMap ? u"upperBound"_s : u"upper_bound"_s) + + uR"((key); + const auto count = Py_ssize_t(std::distance(it, upper)); + PyObject *pyValues = PyList_New(count); + Py_ssize_t idx = 0; + for (; it != upper; ++it, ++idx) { + const auto &cppItem = it.value(); + PyList_SET_ITEM(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem)); + } + PyDict_SetItem(%out, pyKey, pyValues); + Py_DECREF(pyKey); + } + return %out; +)"_s; +} + +// Convert a STL or Qt multi hash to Dict of Lists using equalRange() +static QString cppMultiHashToPyDict(bool isQMultiHash) +{ + return uR"(PyObject *%out = PyDict_New(); + for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ) { + const auto &key = it)"_s + + (isQMultiHash ? qtMapKeyAccessor : stlMapKeyAccessor) + + uR"(; + PyObject *pyKey = %CONVERTTOPYTHON[%INTYPE_0](key); + auto range = %in.equal_range(key); + const auto count = Py_ssize_t(std::distance(range.first, range.second)); + PyObject *pyValues = PyList_New(count); + Py_ssize_t idx = 0; + for (; it != range.second; ++it, ++idx) { + const auto &cppItem = it.value(); + PyList_SET_ITEM(pyValues, idx, %CONVERTTOPYTHON[%INTYPE_1](cppItem)); + } + PyDict_SetItem(%out, pyKey, pyValues); + Py_DECREF(pyKey); + } + return %out; +)"_s; +} + +// Convert Dict of Lists to a STL or Qt multi hash/map +static QString pyDictToCppMultiHash(bool isQMultiHash) +{ + return uR"(PyObject *key; + PyObject *values; + %out.clear(); + Py_ssize_t pos = 0; + while (PyDict_Next(%in, &pos, &key, &values)) { + %OUTTYPE_0 cppKey = %CONVERTTOCPP[%OUTTYPE_0](key); + const Py_ssize_t size = PySequence_Size(values); + for (Py_ssize_t i = 0; i < size; ++i) { + Shiboken::AutoDecRef value(PySequence_GetItem(values, i)); + %OUTTYPE_1 cppValue = %CONVERTTOCPP[%OUTTYPE_1](value); + %out.insert()"_s + + (isQMultiHash ? u"cppKey, cppValue"_s : u"{cppKey, cppValue}"_s) + + uR"(); + } + } +)"_s; +} + +const PredefinedTemplates &predefinedTemplates() +{ + static const PredefinedTemplates result{ + {u"shiboken_conversion_pylong_to_cpp"_s, + u"%out = %OUTTYPE(PyLong_AsLong(%in));\n"_s}, + + // QPair/std::pair + {u"shiboken_conversion_pysequence_to_cpppair"_s, + uR"(%out.first = %CONVERTTOCPP[%OUTTYPE_0](PySequence_Fast_GET_ITEM(%in, 0)); +%out.second = %CONVERTTOCPP[%OUTTYPE_1](PySequence_Fast_GET_ITEM(%in, 1)); +)"_s}, + + {u"shiboken_conversion_cpppair_to_pytuple"_s, + uR"(PyObject *%out = PyTuple_New(2); +PyTuple_SET_ITEM(%out, 0, %CONVERTTOPYTHON[%INTYPE_0](%in.first)); +PyTuple_SET_ITEM(%out, 1, %CONVERTTOPYTHON[%INTYPE_1](%in.second)); +return %out; +)"_s}, + + // Sequential containers + {u"shiboken_conversion_cppsequence_to_pylist"_s, + uR"(PyObject *%out = PyList_New(Py_ssize_t(%in.size())); +Py_ssize_t idx = 0; +for (auto it = std::cbegin(%in), end = std::cend(%in); it != end; ++it, ++idx) { + const auto &cppItem = *it; + PyList_SET_ITEM(%out, idx, %CONVERTTOPYTHON[%INTYPE_0](cppItem)); +} +return %out;)"_s}, + + // PySet + {u"shiboken_conversion_cppsequence_to_pyset"_s, + uR"(PyObject *%out = PySet_New(nullptr); +for (const auto &cppItem : %in) { + PySet_Add(%out, %CONVERTTOPYTHON[%INTYPE_0](cppItem)); +} +return %out;)"_s}, + + {u"shiboken_conversion_pyiterable_to_cppsequentialcontainer"_s, + pySequenceToCppContainer(u"push_back"_s, false)}, + {u"shiboken_conversion_pyiterable_to_cppsequentialcontainer_reserve"_s, + pySequenceToCppContainer(u"push_back"_s, true)}, + {u"shiboken_conversion_pyiterable_to_cpparray"_s, + pySequenceToCppArray()}, + {u"shiboken_conversion_pyiterable_to_cppsetcontainer"_s, + pySequenceToCppContainer(u"insert"_s, false)}, + + // Maps + {u"shiboken_conversion_stdmap_to_pydict"_s, + cppMapToPyDict(false)}, + {u"shiboken_conversion_qmap_to_pydict"_s, + cppMapToPyDict(true)}, + {u"shiboken_conversion_pydict_to_stdmap"_s, + pyDictToCppMap(false)}, + {u"shiboken_conversion_pydict_to_qmap"_s, + pyDictToCppMap(true)}, + + // Multi maps + {u"shiboken_conversion_stdmultimap_to_pydict"_s, + cppMultiMapToPyDict(false)}, + {u"shiboken_conversion_qmultimap_to_pydict"_s, + cppMultiMapToPyDict(true)}, + + // Multi hashes + {u"shiboken_conversion_stdunorderedmultimap_to_pydict"_s, + cppMultiHashToPyDict(false)}, + {u"shiboken_conversion_qmultihash_to_pydict"_s, + cppMultiHashToPyDict(true)}, + + // STL multi hash/map + {u"shiboken_conversion_pydict_to_stdmultimap"_s, + pyDictToCppMultiHash(false)}, + {u"shiboken_conversion_pydict_to_qmultihash"_s, + pyDictToCppMultiHash(true)} + }; + + return result; +} + +QByteArray containerTypeSystemSnippet(const char *name, const char *type, + const char *include, + const char *nativeToTarget, + const char *targetToNativeType, + const char *targetToNative) +{ + QByteArray result = QByteArrayLiteral("<container-type name=\"") + + name + QByteArrayLiteral("\" type=\"") + type + R"("> + <include file-name=")" + include + R"(" location="global"/> + <conversion-rule> + <native-to-target> + <insert-template name=")" + nativeToTarget + R"("/> + </native-to-target> +)"; + if (targetToNativeType != nullptr) { + result += QByteArrayLiteral(R"( <target-to-native> + <add-conversion type=")") + targetToNativeType + + QByteArrayLiteral(R"("> + <insert-template name=")") + targetToNative + QByteArrayLiteral(R"("/> + </add-conversion> + </target-to-native> +)"); + } +result += QByteArrayLiteral(R"( </conversion-rule> +</container-type> +)"); + return result; +} diff --git a/sources/shiboken6/ApiExtractor/predefined_templates.h b/sources/shiboken6/ApiExtractor/predefined_templates.h new file mode 100644 index 000000000..0cc2c7f32 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/predefined_templates.h @@ -0,0 +1,27 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREDEFINED_TEMPLATES_H +#define PREDEFINED_TEMPLATES_H + +#include <QtCore/QList> +#include <QtCore/QString> + +struct PredefinedTemplate +{ + QString name; + QString content; +}; + +using PredefinedTemplates = QList<PredefinedTemplate>; + +const PredefinedTemplates &predefinedTemplates(); + +// Create an XML snippet for a container type. +QByteArray containerTypeSystemSnippet(const char *name, const char *type, + const char *include, + const char *nativeToTarget, + const char *targetToNativeType = nullptr, + const char *targetToNative = nullptr); + +#endif // PREDEFINED_TEMPLATES_H diff --git a/sources/shiboken6/ApiExtractor/primitivetypeentry.h b/sources/shiboken6/ApiExtractor/primitivetypeentry.h new file mode 100644 index 000000000..6faaf7a61 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/primitivetypeentry.h @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PRIMITIVETYPEENTRY_H +#define PRIMITIVETYPEENTRY_H + +#include "typesystem.h" +#include "customconversion_typedefs.h" + +class PrimitiveTypeEntryPrivate; + +/// A PrimitiveTypeEntry is user-defined type with conversion rules, a C++ +/// primitive type for which a PrimitiveTypeConverter exists in libshiboken +/// or a typedef to a C++ primitive type as determined by AbstractMetaBuilder. +class PrimitiveTypeEntry : public TypeEntry +{ +public: + explicit PrimitiveTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + QString defaultConstructor() const; + void setDefaultConstructor(const QString& defaultConstructor); + bool hasDefaultConstructor() const; + + /** + * The PrimitiveTypeEntry pointed by this type entry if it + * represents a typedef). + * \return the type referenced by the typedef, or a null pointer + * if the current object is not an typedef + */ + PrimitiveTypeEntryPtr referencedTypeEntry() const; + + /** + * Defines type referenced by this entry. + * \param referencedTypeEntry type referenced by this entry + */ + void setReferencedTypeEntry(PrimitiveTypeEntryPtr referencedTypeEntry); + + /// Returns whether this entry references another entry. + bool referencesType() const; + + bool preferredTargetLangType() const; + void setPreferredTargetLangType(bool b); + + bool hasCustomConversion() const; + void setCustomConversion(const CustomConversionPtr &customConversion); + CustomConversionPtr customConversion() const; + + TypeEntry *clone() const override; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +protected: + explicit PrimitiveTypeEntry(PrimitiveTypeEntryPrivate *d); +}; + +/// Finds the most basic primitive type that the typedef represents, +/// i.e. a type that is not an typedef'ed. +/// \return the most basic non-typedef'ed primitive type represented +/// by this typedef or self in case it is not a reference. +PrimitiveTypeEntryCPtr basicReferencedTypeEntry(const PrimitiveTypeEntryCPtr &e); +PrimitiveTypeEntryCPtr basicReferencedTypeEntry(const TypeEntryCPtr &e); + +/// Finds the basic primitive type that the typedef represents +/// and was explicitly specified in the type system. +/// \return the basic primitive type that was explicitly specified in +/// the type system. +PrimitiveTypeEntryCPtr basicReferencedNonBuiltinTypeEntry(const PrimitiveTypeEntryCPtr &e); + +#endif // PRIMITIVETYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/propertyspec.cpp b/sources/shiboken6/ApiExtractor/propertyspec.cpp new file mode 100644 index 000000000..32b756fad --- /dev/null +++ b/sources/shiboken6/ApiExtractor/propertyspec.cpp @@ -0,0 +1,347 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "propertyspec.h" +#include "abstractmetalang.h" +#include "abstractmetabuilder_p.h" +#include "abstractmetatype.h" +#include "documentation.h" +#include "messages.h" +#include "complextypeentry.h" +#include "typeinfo.h" + +#include "qtcompat.h" + +#include <QtCore/QHash> + +#ifndef QT_NO_DEBUG_STREAM +# include <QtCore/QDebug> +#endif + +#include <algorithm> + +using namespace Qt::StringLiterals; + +class QPropertySpecData : public QSharedData +{ +public: + QPropertySpecData(const TypeSystemProperty &ts, + const AbstractMetaType &type) : + m_name(ts.name), + m_read(ts.read), + m_write(ts.write), + m_designable(ts.designable), + m_reset(ts.reset), + m_notify(ts.notify), + m_type(type), + m_generateGetSetDef(ts.generateGetSetDef) + { + } + + QString m_name; + QString m_read; + QString m_write; + QString m_designable; + QString m_reset; + QString m_notify; + Documentation m_documentation; + AbstractMetaType m_type; + int m_index = -1; + // Indicates whether actual code is generated instead of relying on libpyside. + bool m_generateGetSetDef = false; +}; + +QPropertySpec::QPropertySpec(const TypeSystemProperty &ts, + const AbstractMetaType &type) : + d(new QPropertySpecData(ts, type)) +{ +} + +QPropertySpec::QPropertySpec(const QPropertySpec &) = default; +QPropertySpec &QPropertySpec::operator=(const QPropertySpec &) = default; +QPropertySpec::QPropertySpec(QPropertySpec &&) noexcept = default; +QPropertySpec &QPropertySpec::operator=(QPropertySpec &&) noexcept = default; +QPropertySpec::~QPropertySpec() = default; + +const AbstractMetaType &QPropertySpec::type() const +{ + return d->m_type; +} + +void QPropertySpec::setType(const AbstractMetaType &t) +{ + if (d->m_type != t) + d->m_type = t; +} + +TypeEntryCPtr QPropertySpec::typeEntry() const +{ + return d->m_type.typeEntry(); +} + +QString QPropertySpec::name() const +{ + return d->m_name; +} + +void QPropertySpec::setName(const QString &name) +{ + if (d->m_name != name) + d->m_name = name; +} + +Documentation QPropertySpec::documentation() const +{ + return d->m_documentation; +} + +void QPropertySpec::setDocumentation(const Documentation &doc) +{ + if (d->m_documentation != doc) + d->m_documentation = doc; +} + +QString QPropertySpec::read() const +{ + return d->m_read; +} + +void QPropertySpec::setRead(const QString &read) +{ + if (d->m_read != read) + d->m_read = read; +} + +QString QPropertySpec::write() const +{ + return d->m_write; +} + +void QPropertySpec::setWrite(const QString &write) +{ + if (d->m_write != write) + d->m_write = write; +} + +bool QPropertySpec::hasWrite() const +{ + return !d->m_write.isEmpty(); +} + +QString QPropertySpec::designable() const +{ + return d->m_designable; +} + +void QPropertySpec::setDesignable(const QString &designable) +{ + if (d->m_designable != designable) + d->m_designable = designable; +} + +QString QPropertySpec::reset() const +{ + return d->m_reset; +} + +void QPropertySpec::setReset(const QString &reset) +{ + if (d->m_reset != reset) + d->m_reset = reset; +} + +QString QPropertySpec::notify() const +{ + return d->m_notify; +} + +void QPropertySpec::setNotify(const QString ¬ify) +{ + if (d->m_notify != notify) + d->m_notify = notify; +} + +int QPropertySpec::index() const +{ + return d->m_index; +} + +void QPropertySpec::setIndex(int index) +{ + if (d->m_index != index) + d->m_index = index; +} + +bool QPropertySpec::generateGetSetDef() const +{ + return d->m_generateGetSetDef; +} + +void QPropertySpec::setGenerateGetSetDef(bool generateGetSetDef) +{ + if (d->m_generateGetSetDef != generateGetSetDef) + d->m_generateGetSetDef = generateGetSetDef; +} + +// Parse a Q_PROPERTY macro +// Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged) +// into a TypeSystemProperty. +TypeSystemProperty QPropertySpec::typeSystemPropertyFromQ_Property(const QString &declarationIn, + QString *errorMessage) +{ + enum class PropertyToken { None, Read, Write, Designable, Reset, Notify, Member }; + + static const QHash<QString, PropertyToken> tokenLookup = { + {"READ"_L1, PropertyToken::Read}, + {"WRITE"_L1, PropertyToken::Write}, + {"DESIGNABLE"_L1, PropertyToken::Designable}, + {"RESET"_L1, PropertyToken::Reset}, + {"NOTIFY"_L1, PropertyToken::Notify}, + {"MEMBER"_L1, PropertyToken::Member} + }; + + errorMessage->clear(); + + TypeSystemProperty result; + + // Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged) + + const QString declaration = declarationIn.simplified(); + auto propertyTokens = declaration.split(u' ', Qt::SkipEmptyParts); + + // To properly parse complicated type declarations like + // "Q_PROPERTY(const QList<QString > *objectName READ objectName ..." + // we first search the first "READ" token, parse the subsequent tokens and + // extract type and name from the tokens before "READ". + const auto it = std::find_if(propertyTokens.cbegin(), propertyTokens.cend(), + [](const QString &t) { return tokenLookup.contains(t); }); + if (it == propertyTokens.cend()) { + *errorMessage = u"Invalid property specification, READ missing"_s; + return result; + } + + const auto firstToken = qsizetype(it - propertyTokens.cbegin()); + if (firstToken < 2) { + *errorMessage = u"Insufficient number of tokens in property specification"_s; + return result; + } + + for (qsizetype pos = firstToken; pos + 1 < propertyTokens.size(); pos += 2) { + switch (tokenLookup.value(propertyTokens.at(pos))) { + case PropertyToken::Read: + result.read = propertyTokens.at(pos + 1); + break; + case PropertyToken::Write: + result.write = propertyTokens.at(pos + 1); + break; + case PropertyToken::Reset: + result.reset = propertyTokens.at(pos + 1); + break; + case PropertyToken::Designable: + result.designable = propertyTokens.at(pos + 1); + break; + case PropertyToken::Notify: + result.notify = propertyTokens.at(pos + 1); + break; + case PropertyToken::Member: + // Ignore MEMBER tokens introduced by QTBUG-16852 as Python + // properties are anyways generated for fields. + return {}; + + case PropertyToken::None: + break; + } + } + + const auto namePos = firstToken - 1; + result.name = propertyTokens.at(namePos); + + result.type = propertyTokens.constFirst(); + for (qsizetype pos = 1; pos < namePos; ++pos) + result.type += u' ' + propertyTokens.at(pos); + + // Fix errors like "Q_PROPERTY(QXYSeries *series .." to be of type "QXYSeries*" + while (!result.name.isEmpty() && !result.name.at(0).isLetter()) { + result.type += result.name.at(0); + result.name.remove(0, 1); + } + if (!result.isValid()) + *errorMessage = u"Incomplete property specification"_s; + return result; +} + +// Create a QPropertySpec from a TypeSystemProperty, determining +// the AbstractMetaType from the type string. +std::optional<QPropertySpec> + QPropertySpec::fromTypeSystemProperty(AbstractMetaBuilderPrivate *b, + const AbstractMetaClassPtr &metaClass, + const TypeSystemProperty &ts, + const QStringList &scopes, + QString *errorMessage) + { + Q_ASSERT(ts.isValid()); + QString typeError; + TypeInfo info = TypeParser::parse(ts.type, &typeError); + if (info.qualifiedName().isEmpty()) { + *errorMessage = msgPropertyTypeParsingFailed(ts.name, ts.type, typeError); + return {}; + } + + auto type = b->translateType(info, metaClass, {}, &typeError); + if (!type.has_value()) { + const QStringList qualifiedName = info.qualifiedName(); + for (auto j = scopes.size(); j >= 0 && !type; --j) { + info.setQualifiedName(scopes.mid(0, j) + qualifiedName); + type = b->translateType(info, metaClass, {}, &typeError); + } + } + + if (!type.has_value()) { + *errorMessage = msgPropertyTypeParsingFailed(ts.name, ts.type, typeError); + return {}; + } + return QPropertySpec(ts, type.value()); + } + +// Convenience to create a QPropertySpec from a Q_PROPERTY macro +// via TypeSystemProperty. +std::optional<QPropertySpec> + QPropertySpec::parseQ_Property(AbstractMetaBuilderPrivate *b, + const AbstractMetaClassPtr &metaClass, + const QString &declarationIn, + const QStringList &scopes, + QString *errorMessage) +{ + const TypeSystemProperty ts = + typeSystemPropertyFromQ_Property(declarationIn, errorMessage); + if (!ts.isValid()) + return {}; + return fromTypeSystemProperty(b, metaClass, ts, scopes, errorMessage); +} + +#ifndef QT_NO_DEBUG_STREAM +void QPropertySpec::formatDebug(QDebug &debug) const +{ + debug << '#' << d->m_index << " \"" << d->m_name << "\" (" << d->m_type.cppSignature(); + debug << "), read=" << d->m_read; + if (!d->m_write.isEmpty()) + debug << ", write=" << d->m_write; + if (!d->m_reset.isEmpty()) + debug << ", reset=" << d->m_reset; + if (!d->m_designable.isEmpty()) + debug << ", designable=" << d->m_designable; + if (!d->m_documentation.isEmpty()) + debug << ", doc=\"" << d->m_documentation << '"'; +} + +QDebug operator<<(QDebug d, const QPropertySpec &p) +{ + QDebugStateSaver s(d); + d.noquote(); + d.nospace(); + d << "QPropertySpec("; + p.formatDebug(d); + d << ')'; + return d; +} +#endif // QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/propertyspec.h b/sources/shiboken6/ApiExtractor/propertyspec.h new file mode 100644 index 000000000..9e2e0f3d4 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/propertyspec.h @@ -0,0 +1,104 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYSPEC_H +#define PROPERTYSPEC_H + +class AbstractMetaType; + +#include "abstractmetalang_typedefs.h" +#include "typesystem_typedefs.h" + +#include <QtCore/QStringList> +#include <QtCore/QSharedDataPointer> + +#include <optional> + +class AbstractMetaClass; +class AbstractMetaBuilderPrivate; +class AbstractMetaType; +class Documentation; +class TypeEntry; + +struct TypeSystemProperty; + +class QPropertySpecData; + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class QPropertySpec +{ +public: + explicit QPropertySpec(const TypeSystemProperty &ts, + const AbstractMetaType &type); + QPropertySpec(const QPropertySpec &); + QPropertySpec &operator=(const QPropertySpec &); + QPropertySpec(QPropertySpec &&) noexcept; + QPropertySpec &operator=(QPropertySpec &&) noexcept; + ~QPropertySpec(); + + static TypeSystemProperty typeSystemPropertyFromQ_Property(const QString &declarationIn, + QString *errorMessage); + + + static std::optional<QPropertySpec> + fromTypeSystemProperty(AbstractMetaBuilderPrivate *b, + const AbstractMetaClassPtr &metaClass, + const TypeSystemProperty &ts, + const QStringList &scopes, + QString *errorMessage); + + static std::optional<QPropertySpec> + parseQ_Property(AbstractMetaBuilderPrivate *b, + const AbstractMetaClassPtr &metaClass, + const QString &declarationIn, + const QStringList &scopes, + QString *errorMessage); + + const AbstractMetaType &type() const; + void setType(const AbstractMetaType &t); + + TypeEntryCPtr typeEntry() const; + + QString name() const; + void setName(const QString &name); + + Documentation documentation() const; + void setDocumentation(const Documentation &doc); + + QString read() const; + void setRead(const QString &read); + + QString write() const; + void setWrite(const QString &write); + bool hasWrite() const; + + QString designable() const; + void setDesignable(const QString &designable); + + QString reset() const; + void setReset(const QString &reset); + + QString notify() const; // Q_PROPERTY/C++ only + void setNotify(const QString ¬ify); + + int index() const; + void setIndex(int index); + + // Indicates whether actual code is generated instead of relying on libpyside. + bool generateGetSetDef() const; + void setGenerateGetSetDef(bool generateGetSetDef); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + +private: + QSharedDataPointer<QPropertySpecData> d; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QPropertySpec &p); +#endif + +#endif // PROPERTYSPEC_H diff --git a/sources/shiboken6/ApiExtractor/pymethoddefentry.cpp b/sources/shiboken6/ApiExtractor/pymethoddefentry.cpp new file mode 100644 index 000000000..64d44378b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/pymethoddefentry.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "pymethoddefentry.h" +#include "textstream.h" + +#include <QtCore/QDebug> + +TextStream &operator<<(TextStream &str, const castToPyCFunction &c) +{ + str << "reinterpret_cast<PyCFunction>(" << c.m_function << ')'; + return str; +} + +TextStream &operator<<(TextStream &s, const PyMethodDefEntry &e) +{ + s << "{\"" << e.name << "\", " << castToPyCFunction(e.function) <<", "; + if (e.methFlags.isEmpty()) { + s << '0'; + } else { + for (qsizetype i = 0, size = e.methFlags.size(); i < size; ++i) { + if (i) + s << '|'; + s << e.methFlags.at(i); + } + } + if (e.doc.isEmpty()) + s << ", nullptr"; + else + s << ", R\"(" << e.doc << ")\""; + s << '}'; + return s; +} + +TextStream &operator<<(TextStream &s, const PyMethodDefEntries &entries) +{ + for (const auto &e : entries) + s << e << ",\n"; + return s; +} + +QDebug operator<<(QDebug debug, const PyMethodDefEntry &e) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + debug << "PyMethodDefEntry(\"" << e.name << "\", " << e.function + << ", " << e.methFlags; + if (!e.doc.isEmpty()) + debug << ", \"" << e.doc << '"'; + debug << ')'; + return debug; +} diff --git a/sources/shiboken6/ApiExtractor/pymethoddefentry.h b/sources/shiboken6/ApiExtractor/pymethoddefentry.h new file mode 100644 index 000000000..a8694eb30 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/pymethoddefentry.h @@ -0,0 +1,38 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PYMETHODDEFENTRY_H +#define PYMETHODDEFENTRY_H + +#include <QtCore/QByteArrayList> +#include <QtCore/QString> + +QT_FORWARD_DECLARE_CLASS(QDebug) + +class TextStream; + +struct castToPyCFunction +{ + explicit castToPyCFunction(QAnyStringView function) noexcept : + m_function(function) {} + + QAnyStringView m_function; +}; + +struct PyMethodDefEntry +{ + QString name; + QString function; + QByteArrayList methFlags; // "METH_O" etc. + QString doc; +}; + +using PyMethodDefEntries = QList<PyMethodDefEntry>; + +TextStream &operator<<(TextStream &str, const castToPyCFunction &e); +TextStream &operator<<(TextStream &s, const PyMethodDefEntry &e); +TextStream &operator<<(TextStream &s, const PyMethodDefEntries &e); + +QDebug operator<<(QDebug debug, const PyMethodDefEntry &e); + +#endif // PYMETHODDEFENTRY_H diff --git a/sources/shiboken6/ApiExtractor/pythontypeentry.h b/sources/shiboken6/ApiExtractor/pythontypeentry.h new file mode 100644 index 000000000..2e0fbda97 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/pythontypeentry.h @@ -0,0 +1,29 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PYTHONTYPEENTRY_H +#define PYTHONTYPEENTRY_H + +#include "customtypenentry.h" +#include "typesystem_enums.h" + +class PythonTypeEntry : public CustomTypeEntry +{ +public: + explicit PythonTypeEntry(const QString &entryName, + const QString &checkFunction, + TypeSystem::CPythonType type); + + TypeEntry *clone() const override; + + TypeSystem::CPythonType cPythonType() const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif + +protected: + explicit PythonTypeEntry(TypeEntryPrivate *d); +}; + +#endif // PYTHONTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/qtcompat.h b/sources/shiboken6/ApiExtractor/qtcompat.h new file mode 100644 index 000000000..3837dcfd2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/qtcompat.h @@ -0,0 +1,37 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTCOMPAT_H +#define QTCOMPAT_H + +#include <QtCore/qtconfigmacros.h> + +#if QT_VERSION < 0x060400 + +// QTBUG-98434, provide literals of Qt 6.4 for compatibility. + +# include <QtCore/QString> + +# define QLatin1StringView QLatin1String + +namespace Qt { +inline namespace Literals { +inline namespace StringLiterals { + +constexpr inline QLatin1String operator"" _L1(const char *str, size_t size) noexcept +{ + return QLatin1String(str, qsizetype(size)); +} + +inline QString operator"" _s(const char16_t *str, size_t size) noexcept +{ + return QString(QStringPrivate(nullptr, const_cast<char16_t *>(str), qsizetype(size))); +} + +} // StringLiterals +} // Literals +} // Qt + +#endif // < 6.4 + +#endif // QTCOMPAT_H diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.cpp b/sources/shiboken6/ApiExtractor/qtdocparser.cpp new file mode 100644 index 000000000..5bd99bbd8 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/qtdocparser.cpp @@ -0,0 +1,445 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtdocparser.h" +#include "classdocumentation.h" +#include "abstractmetaargument.h" +#include "abstractmetaenum.h" +#include "abstractmetafunction.h" +#include "abstractmetalang.h" +#include "abstractmetatype.h" +#include "documentation.h" +#include "modifications.h" +#include "messages.h" +#include "propertyspec.h" +#include "reporthandler.h" +#include "flagstypeentry.h" +#include "complextypeentry.h" +#include "functiontypeentry.h" +#include "enumtypeentry.h" + +#include "qtcompat.h" + +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QUrl> + +using namespace Qt::StringLiterals; + +enum { debugFunctionSearch = 0 }; + +constexpr auto briefStartElement = "<brief>"_L1; +constexpr auto briefEndElement = "</brief>"_L1; +constexpr auto webxmlSuffix = ".webxml"_L1; + +Documentation QtDocParser::retrieveModuleDocumentation() +{ + return retrieveModuleDocumentation(packageName()); +} + +static void formatPreQualifications(QTextStream &str, const AbstractMetaType &type) +{ + if (type.isConstant()) + str << "const " ; +} + +static void formatPostQualifications(QTextStream &str, const AbstractMetaType &type) +{ + if (type.referenceType() == LValueReference) + str << " &"; + else if (type.referenceType() == RValueReference) + str << " &&"; + else if (type.indirections()) + str << ' ' << QByteArray(type.indirections(), '*'); +} + +static void formatFunctionUnqualifiedArgTypeQuery(QTextStream &str, + const AbstractMetaType &metaType) +{ + switch (metaType.typeUsagePattern()) { + case AbstractMetaType::FlagsPattern: { + // Modify qualified name "QFlags<Qt::AlignmentFlag>" with name "Alignment" + // to "Qt::Alignment" as seen by qdoc. + const auto flagsEntry = std::static_pointer_cast<const FlagsTypeEntry>(metaType.typeEntry()); + QString name = flagsEntry->qualifiedCppName(); + if (name.endsWith(u'>') && name.startsWith(u"QFlags<")) { + const int lastColon = name.lastIndexOf(u':'); + if (lastColon != -1) { + name.replace(lastColon + 1, name.size() - lastColon - 1, metaType.name()); + name.remove(0, 7); + } else { + name = metaType.name(); // QFlags<> of enum in global namespace + } + } + str << name; + } + break; + case AbstractMetaType::ContainerPattern: { // QVector<int> + str << metaType.typeEntry()->qualifiedCppName() << '<'; + const auto instantiations = metaType.instantiations(); + for (qsizetype i = 0, size = instantiations.size(); i < size; ++i) { + if (i) + str << ", "; + const auto &instantiation = instantiations.at(i); + formatPreQualifications(str, instantiation); + str << instantiation.typeEntry()->qualifiedCppName(); + formatPostQualifications(str, instantiation); + } + str << '>'; + } + break; + default: // Fully qualify enums (Qt::AlignmentFlag), nested classes, etc. + str << metaType.typeEntry()->qualifiedCppName(); + break; + } +} + +static QString formatFunctionArgTypeQuery(const AbstractMetaType &metaType) +{ + QString result; + QTextStream str(&result);formatPreQualifications(str, metaType); + formatFunctionUnqualifiedArgTypeQuery(str, metaType); + formatPostQualifications(str, metaType); + return result; +} + +QString QtDocParser::functionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaFunctionCPtr &func, + QString *errorMessage) +{ + errorMessage->clear(); + + const QString docString = + queryFunctionDocumentation(sourceFileName, classDocumentation, metaClass, + func, errorMessage); + + const auto funcModifs = DocParser::getXpathDocModifications(func, metaClass); + return docString.isEmpty() || funcModifs.isEmpty() + ? docString : applyDocModifications(funcModifs, docString); +} + +QString QtDocParser::queryFunctionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaFunctionCPtr &func, + QString *errorMessage) +{ + // Search candidates by name and const-ness + FunctionDocumentationList candidates = + classDocumentation.findFunctionCandidates(func->name(), func->isConstant()); + if (candidates.isEmpty()) { + *errorMessage = msgCannotFindDocumentation(sourceFileName, func.get()) + + u" (no matches)"_s; + return {}; + } + + // Try an exact query + FunctionDocumentationQuery fq; + fq.name = func->name(); + fq.constant = func->isConstant(); + for (const auto &arg : func->arguments()) + fq.parameters.append(formatFunctionArgTypeQuery(arg.type())); + + const auto funcFlags = func->flags(); + // Re-add arguments removed by the metabuilder to binary operator functions + if (funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorLeadingClassArgumentRemoved) + || funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorTrailingClassArgumentRemoved)) { + QString classType = metaClass->qualifiedCppName(); + if (!funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorClassArgumentByValue)) { + classType.prepend(u"const "_s); + classType.append(u" &"_s); + } + if (funcFlags.testFlag(AbstractMetaFunction::Flag::OperatorLeadingClassArgumentRemoved)) + fq.parameters.prepend(classType); + else + fq.parameters.append(classType); + } + + const qsizetype index = ClassDocumentation::indexOfFunction(candidates, fq); + + if (debugFunctionSearch) { + qDebug() << __FUNCTION__ << metaClass->name() << fq << funcFlags << "returns" + << index << "\n " << candidates.value(index) << "\n " << candidates; + } + + if (index != -1) + return candidates.at(index).description; + + // Fallback: Try matching by argument count + const auto parameterCount = func->arguments().size(); + auto pend = std::remove_if(candidates.begin(), candidates.end(), + [parameterCount](const FunctionDocumentation &fd) { + return fd.parameters.size() != parameterCount; }); + candidates.erase(pend, candidates.end()); + if (candidates.size() == 1) { + const auto &match = candidates.constFirst(); + QTextStream(errorMessage) << msgFallbackForDocumentation(sourceFileName, func.get()) + << "\n Falling back to \"" << match.signature + << "\" obtained by matching the argument count only."; + return candidates.constFirst().description; + } + + QTextStream(errorMessage) << msgCannotFindDocumentation(sourceFileName, func.get()) + << " (" << candidates.size() << " candidates matching the argument count)"; + return {}; +} + +// Extract the <brief> section from a WebXML (class) documentation and remove it +// from the source. +static QString extractBrief(QString *value) +{ + const auto briefStart = value->indexOf(briefStartElement); + if (briefStart < 0) + return {}; + const auto briefEnd = value->indexOf(briefEndElement, + briefStart + briefStartElement.size()); + if (briefEnd < briefStart) + return {}; + const auto briefLength = briefEnd + briefEndElement.size() - briefStart; + QString briefValue = value->mid(briefStart, briefLength); + briefValue.insert(briefValue.size() - briefEndElement.size(), + u"<rst> More_...</rst>"_s); + value->remove(briefStart, briefLength); + return briefValue; +} + +// Find the webxml file for global functions/enums +// by the doc-file typesystem attribute or via include file. +static QString findGlobalWebXmLFile(const QString &documentationDataDirectory, + const QString &docFile, + const Include &include) +{ + QString result; + if (!docFile.isEmpty()) { + result = documentationDataDirectory + u'/' + docFile; + if (!result.endsWith(webxmlSuffix)) + result += webxmlSuffix; + return QFileInfo::exists(result) ? result : QString{}; + } + if (include.name().isEmpty()) + return {}; + // qdoc "\headerfile <QtLogging>" directive produces "qtlogging.webxml" + result = documentationDataDirectory + u'/' + + QFileInfo(include.name()).baseName() + webxmlSuffix; + if (QFileInfo::exists(result)) + return result; + // qdoc "\headerfile <qdrawutil.h>" produces "qdrawutil-h.webxml" + result.insert(result.size() - webxmlSuffix.size(), "-h"_L1); + return QFileInfo::exists(result) ? result : QString{}; +} + +void QtDocParser::fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &f) +{ + auto te = f->typeEntry(); + if (te == nullptr) + return; + + const QString sourceFileName = + findGlobalWebXmLFile(documentationDataDirectory(), te->docFile(), te->include()); + if (sourceFileName.isEmpty()) + return; + + QString errorMessage; + auto classDocumentationO = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentationO.has_value()) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return; + } + const QString detailed = + functionDocumentation(sourceFileName, classDocumentationO.value(), + {}, f, &errorMessage); + if (!errorMessage.isEmpty()) + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + const Documentation documentation(detailed, {}); + f->setDocumentation(documentation); +} + +void QtDocParser::fillGlobalEnumDocumentation(AbstractMetaEnum &e) +{ + auto te = e.typeEntry(); + const QString sourceFileName = + findGlobalWebXmLFile(documentationDataDirectory(), te->docFile(), te->include()); + if (sourceFileName.isEmpty()) + return; + + QString errorMessage; + auto classDocumentationO = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentationO.has_value()) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return; + } + if (!extractEnumDocumentation(classDocumentationO.value(), e)) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(sourceFileName, {}, e, {}))); + } +} + +void QtDocParser::fillDocumentation(const AbstractMetaClassPtr &metaClass) +{ + if (!metaClass) + return; + + auto context = metaClass->enclosingClass(); + while (context) { + if (!context->enclosingClass()) + break; + context = context->enclosingClass(); + } + + QString sourceFileRoot = documentationDataDirectory() + u'/' + + metaClass->qualifiedCppName().toLower(); + sourceFileRoot.replace(u"::"_s, u"-"_s); + + QFileInfo sourceFile(sourceFileRoot + webxmlSuffix); + if (!sourceFile.exists()) + sourceFile.setFile(sourceFileRoot + ".xml"_L1); + if (!sourceFile.exists()) { + qCWarning(lcShibokenDoc).noquote().nospace() + << "Can't find qdoc file for class " << metaClass->name() << ", tried: " + << QDir::toNativeSeparators(sourceFile.absoluteFilePath()); + return; + } + + const QString sourceFileName = sourceFile.absoluteFilePath(); + QString errorMessage; + + const auto classDocumentationO = parseWebXml(sourceFileName, &errorMessage); + if (!classDocumentationO.has_value()) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return; + } + + const auto &classDocumentation = classDocumentationO.value(); + for (const auto &p : classDocumentation.properties) { + Documentation doc(p.description, p.brief); + metaClass->setPropertyDocumentation(p.name, doc); + } + + QString docString = applyDocModifications(DocParser::getXpathDocModifications(metaClass), + classDocumentation.description); + + if (docString.isEmpty()) { + QString className = metaClass->name(); + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(sourceFileName, "class", className, {}))); + } + const QString brief = extractBrief(&docString); + + Documentation doc; + if (!brief.isEmpty()) + doc.setValue(brief, Documentation::Brief); + doc.setValue(docString); + metaClass->setDocumentation(doc); + + //Functions Documentation + const auto &funcs = DocParser::documentableFunctions(metaClass); + for (const auto &func : funcs) { + const QString detailed = + functionDocumentation(sourceFileName, classDocumentation, + metaClass, func, &errorMessage); + if (!errorMessage.isEmpty()) + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + const Documentation documentation(detailed, {}); + std::const_pointer_cast<AbstractMetaFunction>(func)->setDocumentation(documentation); + } +#if 0 + // Fields + const AbstractMetaFieldList &fields = metaClass->fields(); + for (AbstractMetaField *field : fields) { + if (field->isPrivate()) + return; + + QString query = "/doxygen/compounddef/sectiondef/memberdef/name[text()=\"" + field->name() + "\"]/.."; + Documentation doc = getDocumentation(DocModificationList(), xquery, query); + field->setDocumentation(doc); + } +#endif + // Enums + for (AbstractMetaEnum &meta_enum : metaClass->enums()) { + if (!extractEnumDocumentation(classDocumentation, meta_enum)) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(sourceFileName, metaClass, meta_enum, {}))); + } + } +} + +bool QtDocParser::extractEnumDocumentation(const ClassDocumentation &classDocumentation, + AbstractMetaEnum &meta_enum) +{ + Documentation enumDoc; + const auto index = classDocumentation.indexOfEnum(meta_enum.name()); + if (index == -1) + return false; + QString doc = classDocumentation.enums.at(index).description; + const auto firstPara = doc.indexOf(u"<para>"); + if (firstPara != -1) { + const QString baseClass = QtDocParser::enumBaseClass(meta_enum); + if (baseClass != "Enum"_L1) { + const QString note = "(inherits <teletype>enum."_L1 + baseClass + + "</teletype>) "_L1; + doc.insert(firstPara + 6, note); + } + } + enumDoc.setValue(doc); + meta_enum.setDocumentation(enumDoc); + return true; +} + +static QString qmlReferenceLink(const QFileInfo &qmlModuleFi) +{ + QString result; + QTextStream(&result) << "<para>The module also provides <link" + << R"( type="page" page="https://doc.qt.io/qt-)" << QT_VERSION_MAJOR + << '/' << qmlModuleFi.baseName() << R"(.html")" + << ">QML types</link>.</para>"; + return result; +} + +Documentation QtDocParser::retrieveModuleDocumentation(const QString& name) +{ + // TODO: This method of acquiring the module name supposes that the target language uses + // dots as module separators in package names. Improve this. + QString moduleName = name; + moduleName.remove(0, name.lastIndexOf(u'.') + 1); + if (moduleName == u"QtQuickControls2") + moduleName.chop(1); + const QString prefix = documentationDataDirectory() + u'/' + + moduleName.toLower(); + + const QString sourceFile = prefix + u"-index.webxml"_s; + if (!QFile::exists(sourceFile)) { + qCWarning(lcShibokenDoc).noquote().nospace() + << "Can't find qdoc file for module " << name << ", tried: " + << QDir::toNativeSeparators(sourceFile); + return Documentation(); + } + + QString errorMessage; + QString docString = webXmlModuleDescription(sourceFile, &errorMessage); + if (!errorMessage.isEmpty()) { + qCWarning(lcShibokenDoc, "%s", qPrintable(errorMessage)); + return {}; + } + + Documentation doc(docString, {}); + if (doc.isEmpty()) { + qCWarning(lcShibokenDoc, "%s", + qPrintable(msgCannotFindDocumentation(sourceFile, "module", name))); + return doc; + } + + // If a QML module info file exists, insert a link to the Qt docs. + const QFileInfo qmlModuleFi(prefix + u"-qmlmodule.webxml"_s); + if (qmlModuleFi.isFile()) { + QString docString = doc.detailed(); + const int pos = docString.lastIndexOf(u"</description>"); + if (pos != -1) { + docString.insert(pos, qmlReferenceLink(qmlModuleFi)); + doc.setDetailed(docString); + } + } + + return doc; +} diff --git a/sources/shiboken6/ApiExtractor/qtdocparser.h b/sources/shiboken6/ApiExtractor/qtdocparser.h new file mode 100644 index 000000000..f6ba5e47a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/qtdocparser.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QTDOCPARSER_H +#define QTDOCPARSER_H + +#include "docparser.h" + +struct ClassDocumentation; + +class QtDocParser : public DocParser +{ +public: + QtDocParser() = default; + void fillDocumentation(const AbstractMetaClassPtr &metaClass) override; + void fillGlobalFunctionDocumentation(const AbstractMetaFunctionPtr &f) override; + void fillGlobalEnumDocumentation(AbstractMetaEnum &e) override; + + Documentation retrieveModuleDocumentation() override; + Documentation retrieveModuleDocumentation(const QString& name) override; + +private: + static QString functionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaFunctionCPtr &func, + QString *errorMessage); + + static QString queryFunctionDocumentation(const QString &sourceFileName, + const ClassDocumentation &classDocumentation, + const AbstractMetaClassCPtr &metaClass, + const AbstractMetaFunctionCPtr &func, + QString *errorMessage); + static bool extractEnumDocumentation(const ClassDocumentation &classDocumentation, + AbstractMetaEnum &meta_enum); + +}; + +#endif // QTDOCPARSER_H + diff --git a/sources/shiboken6/ApiExtractor/reporthandler.cpp b/sources/shiboken6/ApiExtractor/reporthandler.cpp new file mode 100644 index 000000000..23066ba21 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/reporthandler.cpp @@ -0,0 +1,194 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "reporthandler.h" +#include "typedatabase.h" + +#include "qtcompat.h" + +#include <QtCore/QElapsedTimer> +#include <QtCore/QSet> +#include <cstring> +#include <cstdarg> +#include <cstdio> + +using namespace Qt::StringLiterals; + +#if defined(_WINDOWS) || defined(NOCOLOR) + #define COLOR_END "" + #define COLOR_WHITE "" + #define COLOR_YELLOW "" + #define COLOR_GREEN "" +#else + #define COLOR_END "\033[0m" + #define COLOR_WHITE "\033[1;37m" + #define COLOR_YELLOW "\033[1;33m" + #define COLOR_GREEN "\033[0;32m" +#endif + +static bool m_silent = false; +static int m_warningCount = 0; +static int m_suppressedCount = 0; +static ReportHandler::DebugLevel m_debugLevel = ReportHandler::NoDebug; +static QSet<QString> m_reportedWarnings; +static QString m_prefix; +static bool m_withinProgress = false; +static QByteArray m_progressMessage; +static int m_step_warning = 0; +static QElapsedTimer m_timer; + +Q_LOGGING_CATEGORY(lcShiboken, "qt.shiboken") +Q_LOGGING_CATEGORY(lcShibokenDoc, "qt.shiboken.doc") + +void ReportHandler::install() +{ + qInstallMessageHandler(ReportHandler::messageOutput); + startTimer(); +} + +void ReportHandler::startTimer() +{ + m_timer.start(); +} + +ReportHandler::DebugLevel ReportHandler::debugLevel() +{ + return m_debugLevel; +} + +void ReportHandler::setDebugLevel(ReportHandler::DebugLevel level) +{ + m_debugLevel = level; +} + +bool ReportHandler::setDebugLevelFromArg(const QString &level) +{ + bool result = true; + if (level == u"sparse") + ReportHandler::setDebugLevel(ReportHandler::SparseDebug); + else if (level == u"medium") + ReportHandler::setDebugLevel(ReportHandler::MediumDebug); + else if (level == u"full") + ReportHandler::setDebugLevel(ReportHandler::FullDebug); + else + result = false; + return result; +} + +int ReportHandler::suppressedCount() +{ + return m_suppressedCount; +} + +int ReportHandler::warningCount() +{ + return m_warningCount; +} + +bool ReportHandler::isSilent() +{ + return m_silent; +} + +void ReportHandler::setSilent(bool silent) +{ + m_silent = silent; +} + +void ReportHandler::setPrefix(const QString &p) +{ + m_prefix = p; +} + +void ReportHandler::messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &text) +{ + // Check for file location separator added by SourceLocation + int fileLocationPos = text.indexOf(u":\t"); + if (type == QtWarningMsg) { + if (m_silent || m_reportedWarnings.contains(text)) + return; + if (auto db = TypeDatabase::instance()) { + const bool suppressed = fileLocationPos >= 0 + ? db->isSuppressedWarning(QStringView{text}.mid(fileLocationPos + 2)) + : db->isSuppressedWarning(text); + if (suppressed) { + ++m_suppressedCount; + return; + } + } + ++m_warningCount; + ++m_step_warning; + m_reportedWarnings.insert(text); + } + QString message = m_prefix; + if (!message.isEmpty()) + message.append(u' '); + const auto prefixLength = message.size(); + message.append(text); + // Replace file location tab by space + if (fileLocationPos >= 0) + message[prefixLength + fileLocationPos + 1] = u' '; + fprintf(stderr, "%s\n", qPrintable(qFormatLogMessage(type, context, message))); +} + +static QByteArray timeStamp() +{ + const qint64 elapsed = m_timer.elapsed(); + return elapsed > 5000 + ? QByteArray::number(elapsed / 1000) + 's' + : QByteArray::number(elapsed) + "ms"; +} + +void ReportHandler::startProgress(const QByteArray& str) +{ + if (m_silent) + return; + + if (m_withinProgress) + endProgress(); + + m_withinProgress = true; + m_progressMessage = str; +} + +static void indentStdout(qsizetype n) +{ + for (qsizetype i = 0; i < n; ++i) + fputc(' ', stdout); +} + +void ReportHandler::endProgress() +{ + if (m_silent) + return; + + m_withinProgress = false; + + std::fputs(m_prefix.toUtf8().constData(), stdout); + const auto ts = timeStamp(); + if (ts.size() < 6) + indentStdout(6 - ts.size()); + std::fputs(" [", stdout); + std::fputs(ts.constData(), stdout); + std::fputs("] ", stdout); + std::fputs(m_progressMessage.constData(), stdout); + if (m_progressMessage.size() < 60) + indentStdout(60 - m_progressMessage.size()); + const char *endMessage = m_step_warning == 0 + ? "[" COLOR_GREEN "OK" COLOR_END "]\n" + : "[" COLOR_YELLOW "WARNING" COLOR_END "]\n"; + std::fputs(endMessage, stdout); + std::fflush(stdout); + m_progressMessage.clear(); + m_step_warning = 0; +} + +QByteArray ReportHandler::doneMessage() +{ + QByteArray result = "Done, " + m_prefix.toUtf8() + ' ' + timeStamp(); + if (m_warningCount) + result += ", " + QByteArray::number(m_warningCount) + " warnings"; + if (m_suppressedCount) + result += " (" + QByteArray::number(m_suppressedCount) + " known issues)"; + return result; +} diff --git a/sources/shiboken6/ApiExtractor/reporthandler.h b/sources/shiboken6/ApiExtractor/reporthandler.h new file mode 100644 index 000000000..b79adfa73 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/reporthandler.h @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef REPORTHANDLER_H +#define REPORTHANDLER_H + +#include <QtCore/QLoggingCategory> +#include <QtCore/QString> + +Q_DECLARE_LOGGING_CATEGORY(lcShiboken) +Q_DECLARE_LOGGING_CATEGORY(lcShibokenDoc) + +class ReportHandler +{ +public: + enum DebugLevel { NoDebug, SparseDebug, MediumDebug, FullDebug }; + + static void install(); + static void startTimer(); + + static DebugLevel debugLevel(); + static void setDebugLevel(DebugLevel level); + static bool setDebugLevelFromArg(const QString &); + + static int warningCount(); + + static int suppressedCount(); + + static void startProgress(const QByteArray &str); + static void endProgress(); + + static bool isDebug(DebugLevel level) + { return debugLevel() >= level; } + + static bool isSilent(); + static void setSilent(bool silent); + + static void setPrefix(const QString &p); + + static QByteArray doneMessage(); + +private: + static void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg); +}; + +#endif // REPORTHANDLER_H diff --git a/sources/shiboken6/ApiExtractor/smartpointertypeentry.h b/sources/shiboken6/ApiExtractor/smartpointertypeentry.h new file mode 100644 index 000000000..7b712fe35 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/smartpointertypeentry.h @@ -0,0 +1,57 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SMARTPOINTERTYPEENTRY_H +#define SMARTPOINTERTYPEENTRY_H + +#include "complextypeentry.h" + +class SmartPointerTypeEntryPrivate; + +struct SmartPointerInstantiation +{ + QString name; // user defined name + TypeEntryCPtr typeEntry; +}; + +class SmartPointerTypeEntry : public ComplexTypeEntry +{ +public: + using Instantiations = QList<SmartPointerInstantiation>; + + explicit SmartPointerTypeEntry(const QString &entryName, + const QString &getterName, + TypeSystem::SmartPointerType type, + const QString &refCountMethodName, + const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeSystem::SmartPointerType smartPointerType() const; + + QString getter() const; + + QString refCountMethodName() const; + + QString valueCheckMethod() const; + void setValueCheckMethod(const QString &); + QString nullCheckMethod() const; + void setNullCheckMethod(const QString &); + QString resetMethod() const; + void setResetMethod(const QString &); + + TypeEntry *clone() const override; + + const Instantiations &instantiations() const; + void setInstantiations(const Instantiations &i); + bool matchesInstantiation(const TypeEntryCPtr &e) const; + + QString getTargetName(const AbstractMetaType &metaType) const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif +protected: + SmartPointerTypeEntry(SmartPointerTypeEntryPrivate *d); +}; + +#endif // SMARTPOINTERTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/sourcelocation.cpp b/sources/shiboken6/ApiExtractor/sourcelocation.cpp new file mode 100644 index 000000000..003f201ac --- /dev/null +++ b/sources/shiboken6/ApiExtractor/sourcelocation.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "sourcelocation.h" +#include <QtCore/QDir> +#include <QtCore/QDebug> + +SourceLocation::SourceLocation() = default; + +SourceLocation::SourceLocation(const QString &file, int l) + : m_fileName(file), m_lineNumber(l) +{ +} + +bool SourceLocation::isValid() const +{ + return m_lineNumber >= 0 && !m_fileName.isEmpty(); +} + +QString SourceLocation::fileName() const +{ + return m_fileName; +} + +void SourceLocation::setFileName(const QString &fileName) +{ + m_fileName = fileName; +} + +int SourceLocation::lineNumber() const +{ + return m_lineNumber; +} + +void SourceLocation::setLineNumber(int lineNumber) +{ + m_lineNumber = lineNumber; +} + +QString SourceLocation::toString() const +{ + QString result; + QTextStream s(&result); + format(s); + return result; +} + +template<class Stream> +void SourceLocation::format(Stream &s) const +{ + if (isValid()) + s << QDir::toNativeSeparators(m_fileName) << ':' << m_lineNumber << ':'; + else + s << "<unknown>"; +} + +QTextStream &operator<<(QTextStream &s, const SourceLocation &l) +{ + if (l.isValid()) { + l.format(s); + s << '\t'; // ":\t" is used by ReportHandler for filtering suppressions + } + return s; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const SourceLocation &l) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + l.format(d); + return d; +} +#endif diff --git a/sources/shiboken6/ApiExtractor/sourcelocation.h b/sources/shiboken6/ApiExtractor/sourcelocation.h new file mode 100644 index 000000000..0b188dca3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/sourcelocation.h @@ -0,0 +1,42 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SOURCE_LOCATION_H +#define SOURCE_LOCATION_H + +#include <QtCore/QString> + +QT_FORWARD_DECLARE_CLASS(QDebug) +QT_FORWARD_DECLARE_CLASS(QTextStream) + +class SourceLocation +{ +public: + explicit SourceLocation(const QString &file, int l); + SourceLocation(); + + bool isValid() const; + + QString fileName() const; + void setFileName(const QString &fileName); + + int lineNumber() const; + void setLineNumber(int lineNumber); + + QString toString() const; + + template<class Stream> + void format(Stream &s) const; + +private: + QString m_fileName; + int m_lineNumber = 0; +}; + +QTextStream &operator<<(QTextStream &s, const SourceLocation &l); + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const SourceLocation &l); +#endif + +#endif // SOURCE_LOCATION_H diff --git a/sources/shiboken6/ApiExtractor/symbols.filter b/sources/shiboken6/ApiExtractor/symbols.filter new file mode 100644 index 000000000..af6c744dd --- /dev/null +++ b/sources/shiboken6/ApiExtractor/symbols.filter @@ -0,0 +1,7 @@ +{ +local: +_ZSt*; +_ZNSt*; +_ZNSs*; +_ZNKSt*; +}; diff --git a/sources/shiboken6/ApiExtractor/templateargumententry.h b/sources/shiboken6/ApiExtractor/templateargumententry.h new file mode 100644 index 000000000..9f2338022 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/templateargumententry.h @@ -0,0 +1,26 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TEMPLATEARGUMENTENTRY_H +#define TEMPLATEARGUMENTENTRY_H + +#include "typesystem.h" + +class TemplateArgumentEntryPrivate; + +class TemplateArgumentEntry : public TypeEntry +{ +public: + explicit TemplateArgumentEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + int ordinal() const; + void setOrdinal(int o); + + TypeEntry *clone() const override; + +protected: + explicit TemplateArgumentEntry(TemplateArgumentEntryPrivate *d); +}; + +#endif // TEMPLATEARGUMENTENTRY_H diff --git a/sources/shiboken6/ApiExtractor/tests/CMakeLists.txt b/sources/shiboken6/ApiExtractor/tests/CMakeLists.txt new file mode 100644 index 000000000..76c014fbb --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/CMakeLists.txt @@ -0,0 +1,66 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +set(CMAKE_AUTORCC ON) + +macro(declare_test testname) + # gone: qt4_automoc("${testname}.cpp") + set(SOURCES "${testname}.cpp") + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${testname}.h") + list(APPEND SOURCES "${testname}.h") + endif () + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${testname}.qrc") + list(APPEND SOURCES "${testname}.qrc") + endif () + + add_executable(${testname} ${SOURCES}) + target_include_directories(${testname} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${apiextractor_SOURCE_DIR} + ) + target_link_libraries(${testname} PRIVATE apiextractor Qt::Test) + add_test(${testname} ${testname}) + if (INSTALL_TESTS) + install(PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${testname} + DESTINATION share/apiextractor${apiextractor_SUFFIX}/tests) + endif() +endmacro(declare_test testname) + +declare_test(testabstractmetaclass) +declare_test(testabstractmetatype) +declare_test(testaddfunction) +declare_test(testarrayargument) +declare_test(testcodeinjection) +declare_test(testcontainer) +declare_test(testconversionoperator) +declare_test(testconversionruletag) +declare_test(testctorinformation) +declare_test(testdroptypeentries) +declare_test(testdtorinformation) +declare_test(testenum) +declare_test(testextrainclude) +declare_test(testfunctiontag) +declare_test(testimplicitconversions) +declare_test(testinserttemplate) +declare_test(testmodifyfunction) +declare_test(testmultipleinheritance) +declare_test(testnamespace) +declare_test(testnestedtypes) +declare_test(testnumericaltypedef) +declare_test(testprimitivetypetag) +declare_test(testrefcounttag) +declare_test(testreferencetopointer) +declare_test(testremovefield) +declare_test(testremoveimplconv) +declare_test(testremoveoperatormethod) +declare_test(testresolvetype) +declare_test(testreverseoperators) +declare_test(testtemplates) +declare_test(testtoposort) +declare_test(testvaluetypedefaultctortag) +declare_test(testvoidarg) +declare_test(testtyperevision) +if (NOT DISABLE_DOCSTRINGS) + declare_test(testmodifydocumentation) +endif() + diff --git a/sources/shiboken6/ApiExtractor/tests/a.xml b/sources/shiboken6/ApiExtractor/tests/a.xml new file mode 100644 index 000000000..3c09d3800 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/a.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" ?> +<!-- Sample for testModifyDocumentation --> +<WebXML> + <document> + <class name="A"> + <description>oi + <brief>Brief description</brief> + <para>Paragraph number 1</para> + <para>Paragraph number 2</para> + <para>Paragraph number 3</para> + </description> + </class> + </document> +</WebXML> diff --git a/sources/shiboken6/ApiExtractor/tests/injectedcode.txt b/sources/shiboken6/ApiExtractor/tests/injectedcode.txt new file mode 100644 index 000000000..872898810 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/injectedcode.txt @@ -0,0 +1,5 @@ +// Bla +// @snippet label +code line +// @snippet label +// Bla diff --git a/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp b/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp new file mode 100644 index 000000000..4b5da0c3a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.cpp @@ -0,0 +1,778 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testabstractmetaclass.h" +#include "abstractmetabuilder.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <usingmember.h> +#include <typesystem.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestAbstractMetaClass::testClassName() +{ + const char cppCode[] = "class ClassName {};"; + const char xmlCode[] = R"(<typesystem package="Foo"> + <value-type name="ClassName"/> +</typesystem>)"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + QCOMPARE(classes[0]->name(), u"ClassName"); +} + +void TestAbstractMetaClass::testClassNameUnderNamespace() +{ + const char cppCode[] = "namespace Namespace { class ClassName {}; }\n"; + const char xmlCode[] = R"XML( + <typesystem package="Foo"> + <namespace-type name="Namespace"> + <value-type name="ClassName"/> + </namespace-type> + </typesystem>)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); // 1 namespace + 1 class + if (classes.constFirst()->name() != u"ClassName") + qSwap(classes[0], classes[1]); + + QCOMPARE(classes[0]->name(), u"ClassName"); + QCOMPARE(classes[0]->qualifiedCppName(), u"Namespace::ClassName"); + QCOMPARE(classes[1]->name(), u"Namespace"); + QVERIFY(classes[1]->isNamespace()); + + // Check ctors info + QVERIFY(classes[0]->hasConstructors()); + QCOMPARE(classes[0]->functions().size(), 2); // default ctor + copy ctor + + auto ctors = classes[0]->queryFunctions(FunctionQueryOption::AnyConstructor); + QCOMPARE(ctors.size(), 2); + if (ctors.constFirst()->minimalSignature() != u"ClassName()") + qSwap(ctors[0], ctors[1]); + + QCOMPARE(ctors[0]->arguments().size(), 0); + QCOMPARE(ctors[0]->minimalSignature(), u"ClassName()"); + QCOMPARE(ctors[1]->arguments().size(), 1); + QCOMPARE(ctors[1]->minimalSignature(), u"ClassName(Namespace::ClassName)"); + + QVERIFY(!classes[0]->hasPrivateDestructor()); + QVERIFY(classes[0]->isCopyConstructible()); // implicit default copy ctor + + // This method is buggy and nobody wants to fix it or needs it fixed :-/ + // QVERIFY(classes[0]->hasNonPrivateConstructor()); +} + +static AbstractMetaFunctionCList virtualFunctions(const AbstractMetaClassCPtr &c) +{ + AbstractMetaFunctionCList result; + const auto &functions = c->functions(); + for (const auto &f : functions) { + if (f->isVirtual()) + result.append(f); + } + return result; +} + +void TestAbstractMetaClass::testVirtualMethods() +{ + const char cppCode[] =R"CPP( +class A { +public: + virtual int pureVirtual() const = 0; +}; +class B : public A {}; +class C : public B { +public: + int pureVirtual() const override { return 0; } +}; +class F final : public C { +public: + int pureVirtual() const final { return 1; } +}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package="Foo"> + <primitive-type name='int'/> + <object-type name='A'/> + <object-type name='B'/> + <object-type name='C'/> + <object-type name='F'/> +</typesystem> +)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 4); + const auto a = AbstractMetaClass::findClass(classes, "A"); + const auto b = AbstractMetaClass::findClass(classes, "B"); + const auto c = AbstractMetaClass::findClass(classes, "C"); + const auto f = AbstractMetaClass::findClass(classes, "F"); + QVERIFY(f); + + QCOMPARE(a->baseClass(), nullptr); + QCOMPARE(b->baseClass(), a); + QCOMPARE(c->baseClass(), b); + QCOMPARE(f->baseClass(), c); + + QCOMPARE(a->functions().size(), 2); // default ctor + the pure virtual method + QCOMPARE(b->functions().size(), 2); + QCOMPARE(c->functions().size(), 2); + QCOMPARE(f->functions().size(), 2); + QVERIFY(f->attributes().testFlag(AbstractMetaClass::FinalCppClass)); + + // implementing class, ownclass, declaringclass + const auto ctorA = a->queryFunctions(FunctionQueryOption::Constructors).constFirst(); + const auto ctorB = b->queryFunctions(FunctionQueryOption::Constructors).constFirst(); + const auto ctorC = c->queryFunctions(FunctionQueryOption::Constructors).constFirst(); + QVERIFY(ctorA->isConstructor()); + QVERIFY(!ctorA->isVirtual()); + QVERIFY(ctorB->isConstructor()); + QVERIFY(!ctorB->isVirtual()); + QVERIFY(ctorC->isConstructor()); + QVERIFY(!ctorC->isVirtual()); + QCOMPARE(ctorA->implementingClass(), a); + QCOMPARE(ctorA->ownerClass(), a); + QCOMPARE(ctorA->declaringClass(), a); + + const auto virtualFunctionsA = virtualFunctions(a); + const auto virtualFunctionsB = virtualFunctions(b); + const auto virtualFunctionsC = virtualFunctions(c); + const auto virtualFunctionsF = virtualFunctions(f); + QCOMPARE(virtualFunctionsA.size(), 1); // Add a pureVirtualMethods method !? + QCOMPARE(virtualFunctionsB.size(), 1); + QCOMPARE(virtualFunctionsC.size(), 1); + QCOMPARE(virtualFunctionsF.size(), 1); + + const auto funcA = virtualFunctionsA.constFirst(); + const auto funcB = virtualFunctionsB.constFirst(); + const auto funcC = virtualFunctionsC.constFirst(); + const auto funcF = virtualFunctionsF.constFirst(); + + QCOMPARE(funcA->ownerClass(), a); + QVERIFY(funcC->isVirtual()); + QCOMPARE(funcB->ownerClass(), b); + QCOMPARE(funcC->ownerClass(), c); + QVERIFY(funcC->cppAttributes().testFlag(FunctionAttribute::Override)); + QVERIFY(funcF->cppAttributes().testFlag(FunctionAttribute::Final)); + + QCOMPARE(funcA->declaringClass(), a); + QCOMPARE(funcB->declaringClass(), a); + QCOMPARE(funcC->declaringClass(), a); + + // The next two tests could return null, because it makes more sense. + // But we have too many code written relying on this behaviour where + // implementingClass is equals to declaringClass on pure virtual functions + QCOMPARE(funcA->implementingClass(), a); + QCOMPARE(funcB->implementingClass(), a); + QCOMPARE(funcC->implementingClass(), c); +} + +void TestAbstractMetaClass::testVirtualBase() +{ + const char cppCode[] =R"CPP( +class Base { +public: + virtual ~Base() = default; +}; +class Derived : public Base {}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package="Foo"> + <object-type name='Base'/> + <object-type name='Derived'/> +</typesystem> +)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto base = AbstractMetaClass::findClass(classes, "Base"); + QVERIFY(base); + QVERIFY(base->isPolymorphic()); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); + QVERIFY(derived); + QVERIFY(derived->isPolymorphic()); +} + +void TestAbstractMetaClass::testDefaultValues() +{ + const char cppCode[] = "\ + struct A {\n\ + class B {};\n\ + void method(B b = B());\n\ + };\n"; + const char xmlCode[] = R"XML( + <typesystem package="Foo"> + <value-type name='A'> + <value-type name='B'/> + </value-type> + </typesystem>)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto candidates = classA->queryFunctionsByName(u"method"_s); + QCOMPARE(candidates.size(), 1); + const auto &method = candidates.constFirst(); + const AbstractMetaArgument &arg = method->arguments().constFirst(); + QCOMPARE(arg.defaultValueExpression(), arg.originalDefaultValueExpression()); +} + +void TestAbstractMetaClass::testModifiedDefaultValues() +{ + const char cppCode[] = "\ + struct A {\n\ + class B {};\n\ + void method(B b = B());\n\ + };\n"; + const char xmlCode[] = R"XML( + <typesystem package="Foo"> + <value-type name='A'> + <modify-function signature='method(A::B)'> + <modify-argument index='1'> + <replace-default-expression with='Hello'/> + </modify-argument> + </modify-function> + <value-type name='B'/> + </value-type> + </typesystem>)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto methodMatches = classA->queryFunctionsByName(u"method"_s); + QCOMPARE(methodMatches.size(), 1); + const auto method = methodMatches.constFirst(); + const AbstractMetaArgument &arg = method->arguments().constFirst(); + QCOMPARE(arg.defaultValueExpression(), u"Hello"); + QCOMPARE(arg.originalDefaultValueExpression(), u"A::B()"); +} + +void TestAbstractMetaClass::testInnerClassOfAPolymorphicOne() +{ + const char cppCode[] = "\ + struct A {\n\ + class B {};\n\ + virtual void method();\n\ + };\n"; + const char xmlCode[] = R"XML( + <typesystem package="Foo"> + <object-type name='A'> + <value-type name='B'/> + </object-type> + </typesystem>)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QVERIFY(classA->isPolymorphic()); + const auto classB = AbstractMetaClass::findClass(classes, "A::B"); + QVERIFY(classB); + QVERIFY(!classB->isPolymorphic()); +} + +void TestAbstractMetaClass::testForwardDeclaredInnerClass() +{ + const char cppCode[] ="\ + class A {\n\ + class B;\n\ + };\n\ + class A::B {\n\ + public:\n\ + void foo();\n\ + };\n"; + const char xmlCode[] = R"XML( + <typesystem package="Foo"> + <value-type name='A'> + <value-type name='B'/> + </value-type> + </typesystem>)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto classB = AbstractMetaClass::findClass(classes, "A::B"); + QVERIFY(classB); + const auto fooF = classB->findFunction("foo"); + QVERIFY(fooF); +} + +void TestAbstractMetaClass::testSpecialFunctions() +{ + const char cppCode[] ="\ + struct A {\n\ + A();\n\ + A(const A&);\n\ + A &operator=(const A&);\n\ + };\n\ + struct B {\n\ + B();\n\ + B(const B &);\n\ + B &operator=(B);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <object-type name='A'/>\n\ + <object-type name='B'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + auto ctors = classA->queryFunctions(FunctionQueryOption::AnyConstructor); + QCOMPARE(ctors.size(), 2); + QCOMPARE(ctors.constFirst()->functionType(), AbstractMetaFunction::ConstructorFunction); + QCOMPARE(ctors.at(1)->functionType(), AbstractMetaFunction::CopyConstructorFunction); + auto assigmentOps = classA->queryFunctionsByName(u"operator="_s); + QCOMPARE(assigmentOps.size(), 1); + QCOMPARE(assigmentOps.constFirst()->functionType(), + AbstractMetaFunction::AssignmentOperatorFunction); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + ctors = classB->queryFunctions(FunctionQueryOption::AnyConstructor); + QCOMPARE(ctors.size(), 2); + QCOMPARE(ctors.constFirst()->functionType(), AbstractMetaFunction::ConstructorFunction); + QCOMPARE(ctors.at(1)->functionType(), AbstractMetaFunction::CopyConstructorFunction); + assigmentOps = classA->queryFunctionsByName(u"operator="_s); + QCOMPARE(assigmentOps.size(), 1); + QCOMPARE(assigmentOps.constFirst()->functionType(), AbstractMetaFunction::AssignmentOperatorFunction); +} + +void TestAbstractMetaClass::testClassDefaultConstructors() +{ + const char cppCode[] = "\ + struct A {};\n\ + \n\ + struct B {\n\ + B();\n\ + private: \n\ + B(const B&);\n\ + };\n\ + \n\ + struct C {\n\ + C(const C&);\n\ + };\n\ + \n\ + struct D {\n\ + private: \n\ + D(const D&);\n\ + };\n\ + \n\ + struct E {\n\ + private: \n\ + ~E();\n\ + };\n\ + \n\ + struct F {\n\ + F(int, int);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'/>\n\ + <object-type name='B'/>\n\ + <value-type name='C'/>\n\ + <object-type name='D'/>\n\ + <object-type name='E'/>\n\ + <value-type name='F'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 6); + + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->functions().size(), 2); + + auto ctors = classA->queryFunctions(FunctionQueryOption::AnyConstructor); + QCOMPARE(ctors.size(), 2); + if (ctors.constFirst()->minimalSignature() != u"A()") + qSwap(ctors[0], ctors[1]); + + QCOMPARE(ctors[0]->arguments().size(), 0); + QCOMPARE(ctors[0]->minimalSignature(), u"A()"); + QCOMPARE(ctors[1]->arguments().size(), 1); + QCOMPARE(ctors[1]->minimalSignature(), u"A(A)"); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + QCOMPARE(classB->functions().size(), 2); + QCOMPARE(classB->functions().constFirst()->minimalSignature(), u"B()"); + + const auto classC = AbstractMetaClass::findClass(classes, "C"); + QVERIFY(classC); + QCOMPARE(classC->functions().size(), 1); + QCOMPARE(classC->functions().constFirst()->minimalSignature(), u"C(C)"); + + const auto classD = AbstractMetaClass::findClass(classes, "D"); + QVERIFY(classD); + QCOMPARE(classD->functions().size(), 1); + QCOMPARE(classD->functions().constFirst()->minimalSignature(), u"D(D)"); + QVERIFY(classD->functions().constFirst()->isPrivate()); + + const auto classE = AbstractMetaClass::findClass(classes, "E"); + QVERIFY(classE); + QVERIFY(classE->hasPrivateDestructor()); + QCOMPARE(classE->functions().size(), 0); + + const auto classF = AbstractMetaClass::findClass(classes, "F"); + QVERIFY(classF); + + ctors = classF->queryFunctions(FunctionQueryOption::AnyConstructor); + QCOMPARE(ctors.size(), 2); + if (ctors.constFirst()->minimalSignature() != u"F(int,int)") + qSwap(ctors[0], ctors[1]); + + QCOMPARE(ctors[0]->arguments().size(), 2); + QCOMPARE(ctors[0]->minimalSignature(), u"F(int,int)"); + QCOMPARE(ctors[1]->arguments().size(), 1); + QCOMPARE(ctors[1]->minimalSignature(), u"F(F)"); +} + +void TestAbstractMetaClass::testClassInheritedDefaultConstructors() +{ + const char cppCode[] = "\ + struct A {\n\ + A();\n\ + private: \n\ + A(const A&);\n\ + };\n\ + struct B : public A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <object-type name='A'/>\n\ + <object-type name='B'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + + auto ctors = classA->queryFunctions(FunctionQueryOption::AnyConstructor); + QCOMPARE(ctors.size(), 2); + if (ctors.constFirst()->minimalSignature() != u"A()") + qSwap(ctors[0], ctors[1]); + + QCOMPARE(ctors[0]->arguments().size(), 0); + QCOMPARE(ctors[0]->minimalSignature(), u"A()"); + QCOMPARE(ctors[1]->arguments().size(), 1); + QCOMPARE(ctors[1]->minimalSignature(), u"A(A)"); + QVERIFY(ctors[1]->isPrivate()); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + + ctors = classB->queryFunctions(FunctionQueryOption::Constructors); + QCOMPARE(ctors.size(), 1); + QCOMPARE(ctors.constFirst()->arguments().size(), 0); + QCOMPARE(ctors.constFirst()->minimalSignature(), u"B()"); +} + +void TestAbstractMetaClass::testAbstractClassDefaultConstructors() +{ + const char cppCode[] = "\ + struct A {\n\ + virtual void method() = 0;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <object-type name='A'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + + const auto ctors = classA->queryFunctions(FunctionQueryOption::Constructors); + QCOMPARE(ctors.size(), 1); + QCOMPARE(ctors.constFirst()->arguments().size(), 0); + QCOMPARE(ctors.constFirst()->minimalSignature(), u"A()"); +} + +void TestAbstractMetaClass::testObjectTypesMustNotHaveCopyConstructors() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <object-type name='A'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + + const auto ctors = classA->queryFunctions(FunctionQueryOption::Constructors); + QCOMPARE(ctors.size(), 1); + QCOMPARE(ctors.constFirst()->arguments().size(), 0); + QCOMPARE(ctors.constFirst()->minimalSignature(), u"A()"); +} + +void TestAbstractMetaClass::testIsPolymorphic() +{ + const char cppCode[] = "\ + class A\n\ + {\n\ + public:\n\ + A();\n\ + inline bool abc() const { return false; }\n\ + };\n\ + \n\ + class B : public A\n\ + {\n\ + public:\n\ + B();\n\ + inline bool abc() const { return false; }\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='bool'/>\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto b = AbstractMetaClass::findClass(classes, "A"); + + QVERIFY(!b->isPolymorphic()); + const auto a = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(!a->isPolymorphic()); +} + +void TestAbstractMetaClass::testClassTypedefedBaseClass() +{ + const char cppCode[] =R"CPP( +class Base { +}; + +using BaseAlias1 = Base; +using BaseAlias2 = BaseAlias1; + +class Derived : public BaseAlias2 { +}; +)CPP"; + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <object-type name='Base'/> + <object-type name='Derived'/> +</typesystem> +)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto base = AbstractMetaClass::findClass(classes, "Base"); + QVERIFY(base); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); + QVERIFY(derived); + QCOMPARE(derived->baseClasses().value(0), base); +} + +void TestAbstractMetaClass::testFreeOperators_data() +{ + QTest::addColumn<QByteArray>("code"); + + const QByteArray classHeader(R"CPP( +class Value +{ +public: + Value(int v) : m_value(v) {} + int value() const { return m_value; } +)CPP"); + + const QByteArray classFooter(R"CPP( +private: + int m_value; +}; +)CPP"); + + const QByteArray multOperatorSignature("Value operator*(const Value &v1, const Value &v2)"); + const QByteArray multOperatorBody("{ return Value(v1.value() * v2.value()); }"); + const QByteArray multOperator = multOperatorSignature + '\n' + multOperatorBody; + + QTest::newRow("free") + << (classHeader + classFooter + "\ninline " + multOperator); + QTest::newRow("free-friend-declared") + << (classHeader + "\n friend " + multOperatorSignature + ";\n" + classFooter + + "\ninline " + multOperator); + QTest::newRow("hidden friend") + << (classHeader + " friend inline " + multOperator + classFooter); +}; + +void TestAbstractMetaClass::testFreeOperators() +{ + QFETCH(QByteArray, code); + const char xmlCode[] = R"XML( + <typesystem package="Foo"> + <primitive-type name="int"/> + <value-type name="Value"/> + </typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(code.constData(), xmlCode)); + QVERIFY(builder); + const auto classes = builder->classes(); + QCOMPARE(classes.size(), 1); + QVERIFY(classes.constFirst()->hasArithmeticOperatorOverload()); + FunctionQueryOptions opts(FunctionQueryOption::OperatorOverloads); + QCOMPARE(classes.constFirst()->queryFunctions(opts).size(), 1); +} + +void TestAbstractMetaClass::testUsingMembers() +{ + const char cppCode[] =R"CPP( +class Base { +public: + explicit Base(int); + +protected: + void member(); +}; + +class Derived : public Base { +public: + using Base::Base; + using Base::member; +}; +)CPP"; + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='Derived'/> +</typesystem> +)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto base = AbstractMetaClass::findClass(classes, "Base"); + QVERIFY(base); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); + QVERIFY(derived); + const auto usingMembers = derived->usingMembers(); + QCOMPARE(usingMembers.size(), 2); + for (const auto &um : usingMembers) { + QCOMPARE(um.access, Access::Public); + QCOMPARE(um.baseClass, base); + QVERIFY(um.memberName == u"Base" || um.memberName == u"member"); + } +} + +void TestAbstractMetaClass::testUsingTemplateMembers_data() +{ + const QByteArray cppCode(R"CPP( +struct Value { + int value = 0; +}; + +template <class T> class List { +public: + List(); + void append(const T &t); +}; + +class ValueList : public List<Value> { +public: + void append(const Value &v1, const Value &v2); +)CPP"); + + QTest::addColumn<QByteArray>("code"); + QTest::newRow("simple") + << (cppCode + "using List::append;\n};\n"); + QTest::newRow("with-template-params") + << (cppCode + "using List<Value>::append;\n};\n"); +} + +void TestAbstractMetaClass::testUsingTemplateMembers() +{ + QFETCH(QByteArray, code); + + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <value-type name='Value'/> + <container-type name='List' type='list'/> + <value-type name='ValueList'/> +</typesystem> +)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(code.constData(), xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto valueList = AbstractMetaClass::findClass(classes, "ValueList"); + QVERIFY(valueList); + auto list = valueList->templateBaseClass(); + QVERIFY(valueList->isUsingMember(list, u"append"_s, Access::Public)); + QCOMPARE(valueList->queryFunctionsByName(u"append"_s).size(), 2); +} + +void TestAbstractMetaClass::testGenerateFunctions() +{ + const char cppCode[] = R"CPP( +class TestClass { +public: + TestClass(); + + void alpha(int); + void beta(int); + void beta(double); + void gamma(int); +}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <object-type name='TestClass' generate-functions='beta(double);gamma'/> +</typesystem> +)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto tc = AbstractMetaClass::findClass(classes, "TestClass"); + // Verify that the constructor and 2 functions are generated. + const auto &functions = tc->functions(); + QCOMPARE(functions.size(), 5); + const auto generateCount = + std::count_if(functions.cbegin(), functions.cend(), + [](const auto &af) { return af->generateBinding(); }); + QCOMPARE(generateCount, 3); +} + +QTEST_APPLESS_MAIN(TestAbstractMetaClass) diff --git a/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.h b/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.h new file mode 100644 index 000000000..a6bd2bf06 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testabstractmetaclass.h @@ -0,0 +1,38 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTABSTRACTMETACLASS_H +#define TESTABSTRACTMETACLASS_H + +#include <QtCore/QObject> + +class AbstractMetaBuilder; + +class TestAbstractMetaClass : public QObject +{ + Q_OBJECT +private slots: + void testClassName(); + void testClassNameUnderNamespace(); + void testVirtualMethods(); + void testVirtualBase(); + void testDefaultValues(); + void testModifiedDefaultValues(); + void testInnerClassOfAPolymorphicOne(); + void testForwardDeclaredInnerClass(); + void testSpecialFunctions(); + void testClassDefaultConstructors(); + void testClassInheritedDefaultConstructors(); + void testAbstractClassDefaultConstructors(); + void testObjectTypesMustNotHaveCopyConstructors(); + void testIsPolymorphic(); + void testClassTypedefedBaseClass(); + void testFreeOperators_data(); + void testFreeOperators(); + void testUsingMembers(); + void testUsingTemplateMembers_data(); + void testUsingTemplateMembers(); + void testGenerateFunctions(); +}; + +#endif // TESTABSTRACTMETACLASS_H diff --git a/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp b/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp new file mode 100644 index 000000000..2c320c874 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.cpp @@ -0,0 +1,229 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testabstractmetatype.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <typesystem.h> +#include <parser/codemodel.h> +#include <typeparser.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestAbstractMetaType::parsing_data() +{ + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("output"); + QTest::newRow("primitive") + << QString::fromLatin1("int") << QString::fromLatin1("int"); + QTest::newRow("ref") + << QString::fromLatin1("int &") << QString::fromLatin1("int&"); + QTest::newRow("pointer") + << QString::fromLatin1("int **") << QString::fromLatin1("int**"); + QTest::newRow("const ref") + << QString::fromLatin1("const int &") << QString::fromLatin1("const int&"); + QTest::newRow("const pointer") + << QString::fromLatin1("const int **") << QString::fromLatin1("const int**"); + QTest::newRow("const pointer const") + << QString::fromLatin1("const int *const*") << QString::fromLatin1("const int*const*"); +} + +void TestAbstractMetaType::parsing() +{ + QFETCH(QString, input); + QFETCH(QString, output); + QString errorMessage; + const TypeInfo ti = TypeParser::parse(input, &errorMessage); + QVERIFY2(errorMessage.isEmpty(), qPrintable(errorMessage)); + const QString actual = ti.toString(); + QCOMPARE(actual, output); +} + +void TestAbstractMetaType::testConstCharPtrType() +{ + const char cppCode[] = "const char* justAtest();\n"; + const char xmlCode[] = "<typesystem package=\"Foo\">\n\ + <primitive-type name='char'/>\n\ + <function signature='justAtest()' />\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + QCOMPARE(builder->globalFunctions().size(), 1); + const auto func = builder->globalFunctions().constFirst(); + AbstractMetaType rtype = func->type(); + // Test properties of const char* + QVERIFY(!rtype.isVoid()); + QCOMPARE(rtype.package(), u"Foo"); + QCOMPARE(rtype.name(), u"char"); + QVERIFY(rtype.isConstant()); + QVERIFY(!rtype.isArray()); + QVERIFY(!rtype.isContainer()); + QVERIFY(!rtype.isObject()); + QVERIFY(!rtype.isPrimitive()); // const char* differs from char, so it's not considered a primitive type by apiextractor + QVERIFY(rtype.isNativePointer()); + QCOMPARE(rtype.referenceType(), NoReference); + QVERIFY(!rtype.isValue()); + QVERIFY(!rtype.isValuePointer()); +} + +void TestAbstractMetaType::testApiVersionSupported() +{ + const char cppCode[] = "class foo {}; class foo2 {};\n\ + void justAtest(); void justAtest3();\n"; + const char xmlCode[] = "<typesystem package='Foo'>\n\ + <value-type name='foo' since='0.1'/>\n\ + <value-type name='foo2' since='1.0'/>\n\ + <value-type name='foo3' since='1.1'/>\n\ + <function signature='justAtest()' since='0.1'/>\n\ + <function signature='justAtest2()' since='1.1'/>\n\ + <function signature='justAtest3()'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + false, u"1.0"_s)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + + + QCOMPARE(builder->globalFunctions().size(), 2); +} + + +void TestAbstractMetaType::testApiVersionNotSupported() +{ + const char cppCode[] = "class object {};\n"; + const char xmlCode[] = "<typesystem package='Foo'>\n\ + <value-type name='object' since='0.1'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + true, u"0.1"_s)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); +} + +void TestAbstractMetaType::testCharType() +{ + const char cppCode[] = "char justAtest(); class A {};\n"; + const char xmlCode[] = "<typesystem package=\"Foo\">\n\ + <primitive-type name='char'/>\n\ + <value-type name='A'/>\n\ + <function signature='justAtest()'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + QCOMPARE(classes.constFirst()->package(), u"Foo"); + + const auto functions = builder->globalFunctions(); + QCOMPARE(functions.size(), 1); + const auto func = functions.constFirst(); + AbstractMetaType rtype = func->type(); + // Test properties of const char* + QVERIFY(!rtype.isVoid()); + QCOMPARE(rtype.package(), u"Foo"); + QCOMPARE(rtype.name(), u"char"); + QVERIFY(!rtype.isConstant()); + QVERIFY(!rtype.isArray()); + QVERIFY(!rtype.isContainer()); + QVERIFY(!rtype.isObject()); + QVERIFY(rtype.isPrimitive()); + QVERIFY(!rtype.isNativePointer()); + QCOMPARE(rtype.referenceType(), NoReference); + QVERIFY(!rtype.isValue()); + QVERIFY(!rtype.isValuePointer()); +} + +void TestAbstractMetaType::testTypedef() +{ + const char cppCode[] = "\ + struct A {\n\ + void someMethod();\n\ + };\n\ + typedef A B;\n\ + typedef B C;\n"; + const char xmlCode[] = "<typesystem package=\"Foo\">\n\ + <value-type name='C' />\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto c = AbstractMetaClass::findClass(classes, "C"); + QVERIFY(c); + QVERIFY(c->isTypeDef()); +} + +void TestAbstractMetaType::testTypedefWithTemplates() +{ + const char cppCode[] = "\ + template<typename T>\n\ + class A {};\n\ + \n\ + class B {};\n\ + typedef A<B> C;\n\ + \n\ + void func(C c);\n"; + const char xmlCode[] = "<typesystem package=\"Foo\">\n\ + <container-type name='A' type='list'/>\n\ + <value-type name='B' />\n\ + <function signature='func(A<B>)'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto functions = builder->globalFunctions(); + QCOMPARE(functions.size(), 1); + const auto function = functions.constFirst(); + AbstractMetaArgumentList args = function->arguments(); + QCOMPARE(args.size(), 1); + const AbstractMetaArgument &arg = args.constFirst(); + AbstractMetaType metaType = arg.type(); + QCOMPARE(metaType.cppSignature(), u"A<B>"); +} + + +void TestAbstractMetaType::testObjectTypeUsedAsValue() +{ + const char cppCode[] = "\ + class A {\n\ + void method(A);\n\ + };\n"; + const char xmlCode[] = "<typesystem package='Foo'>\n\ + <object-type name='A'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto overloads = classA->queryFunctionsByName(u"method"_s); + QCOMPARE(overloads.size(), 1); + const auto method = overloads.constFirst(); + QVERIFY(method); + AbstractMetaArgumentList args = method->arguments(); + QCOMPARE(args.size(), 1); + const AbstractMetaArgument &arg = args.constFirst(); + AbstractMetaType metaType = arg.type(); + QCOMPARE(metaType.cppSignature(), u"A"); + QVERIFY(metaType.isValue()); + QVERIFY(metaType.typeEntry()->isObject()); +} + +QTEST_APPLESS_MAIN(TestAbstractMetaType) diff --git a/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.h b/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.h new file mode 100644 index 000000000..fdcf0c787 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testabstractmetatype.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTABSTRACTMETATYPE_H +#define TESTABSTRACTMETATYPE_H + +#include <QtCore/QObject> + +class TestAbstractMetaType : public QObject +{ + Q_OBJECT +private slots: + void parsing_data(); + void parsing(); + void testConstCharPtrType(); + void testCharType(); + void testTypedef(); + void testTypedefWithTemplates(); + void testApiVersionSupported(); + void testApiVersionNotSupported(); + void testObjectTypeUsedAsValue(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp b/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp new file mode 100644 index 000000000..a891e1e28 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testaddfunction.cpp @@ -0,0 +1,522 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testaddfunction.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <codesnip.h> +#include <addedfunction.h> +#include <addedfunction_p.h> +#include <complextypeentry.h> +#include <primitivetypeentry.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +static constexpr auto voidT = "void"_L1; + +void TestAddFunction::testParsingFuncNameAndConstness() +{ + // generic test... + static constexpr auto sig1 = "func(type1, const type2, const type3* const)"_L1; + QString errorMessage; + auto f1 = AddedFunction::createAddedFunction(sig1, voidT, &errorMessage); + QVERIFY2(f1, qPrintable(errorMessage)); + QCOMPARE(f1->name(), u"func"); + QCOMPARE(f1->arguments().size(), 3); + TypeInfo retval = f1->returnType(); + QCOMPARE(retval.qualifiedName(), QStringList{voidT}); + QCOMPARE(retval.indirections(), 0); + QCOMPARE(retval.isConstant(), false); + QCOMPARE(retval.referenceType(), NoReference); + + // test with a ugly template as argument and other ugly stuff + static constexpr auto sig2 = + " _fu__nc_ ( type1, const type2, const Abc<int& , C<char*> * >" + " * *@my_name@, const type3* const ) const "_L1; + auto f2 = AddedFunction::createAddedFunction(sig2, + u"const Abc<int& , C<char*> * > * *"_s, + &errorMessage); + QVERIFY2(f2, qPrintable(errorMessage)); + QCOMPARE(f2->name(), u"_fu__nc_"); + const auto &args = f2->arguments(); + QCOMPARE(args.size(), 4); + retval = f2->returnType(); + QCOMPARE(retval.qualifiedName(), QStringList{u"Abc"_s}); + QCOMPARE(retval.instantiations().size(), 2); + QCOMPARE(retval.toString(), u"const Abc<int&, C<char*>*>**"); + QCOMPARE(retval.indirections(), 2); + QCOMPARE(retval.isConstant(), true); + QCOMPARE(retval.referenceType(), NoReference); + QVERIFY(args.at(0).name.isEmpty()); + QVERIFY(args.at(1).name.isEmpty()); + + QCOMPARE(args.at(2).name, u"my_name"); + auto arg2Type = args.at(2).typeInfo; + QCOMPARE(arg2Type.qualifiedName(), QStringList{u"Abc"_s}); + QCOMPARE(arg2Type.instantiations().size(), 2); + QCOMPARE(arg2Type.toString(), u"const Abc<int&, C<char*>*>**"); + QCOMPARE(arg2Type.indirections(), 2); + QCOMPARE(arg2Type.isConstant(), true); + QCOMPARE(arg2Type.referenceType(), NoReference); + + QVERIFY(args.at(3).name.isEmpty()); + + // function with no args. + auto f3 = AddedFunction::createAddedFunction("func()"_L1, voidT, &errorMessage); + QVERIFY2(f3, qPrintable(errorMessage)); + QCOMPARE(f3->name(), u"func"); + QCOMPARE(f3->arguments().size(), 0); + + // const call operator + auto f4 = AddedFunction::createAddedFunction("operator()(int)const"_L1, + "int"_L1, &errorMessage); + QVERIFY2(f4, qPrintable(errorMessage)); + QCOMPARE(f4->name(), u"operator()"); + QCOMPARE(f4->arguments().size(), 1); + QVERIFY(f4->isConstant()); +} + +void TestAddFunction::testAddFunction() +{ + const char cppCode[] = R"CPP( +struct B {}; +struct A { + void a(int); +};)CPP"; + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <primitive-type name='float'/> + <value-type name='B'/> + <value-type name='A'> + <add-function signature='b(int, float = 4.6, const B&)' return-type='int' access='protected'/> + <add-function signature='operator()(int)' return-type='int' access='public'/> + </value-type> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + auto *typeDb = TypeDatabase::instance(); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + // default ctor, default copy ctor, func a() and the added functions + QCOMPARE(classA->functions().size(), 5); + + auto addedFunc = classA->findFunction("b"); + QVERIFY(addedFunc); + QCOMPARE(addedFunc->access(), Access::Protected); + QCOMPARE(addedFunc->functionType(), AbstractMetaFunction::NormalFunction); + QVERIFY(addedFunc->isUserAdded()); + QCOMPARE(addedFunc->ownerClass(), classA); + QCOMPARE(addedFunc->implementingClass(), classA); + QCOMPARE(addedFunc->declaringClass(), classA); + QVERIFY(!addedFunc->isVirtual()); + QVERIFY(!addedFunc->isSignal()); + QVERIFY(!addedFunc->isSlot()); + QVERIFY(!addedFunc->isStatic()); + + AbstractMetaType returnType = addedFunc->type(); + QCOMPARE(returnType.typeEntry(), typeDb->findPrimitiveType(u"int"_s)); + const AbstractMetaArgumentList &args = addedFunc->arguments(); + QCOMPARE(args.size(), 3); + QCOMPARE(args.at(0).type().typeEntry(), returnType.typeEntry()); + QCOMPARE(args.at(1).defaultValueExpression(), u"4.6"); + QCOMPARE(args.at(2).type().typeEntry(), typeDb->findType(u"B"_s)); + + auto addedCallOperator = classA->findFunction("operator()"); + QVERIFY(addedCallOperator); +} + +void TestAddFunction::testAddFunctionConstructor() +{ + const char cppCode[] = "struct A { A() {} };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'>\n\ + <add-function signature='A(int)'/>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->functions().size(), 3); // default and added ctors + const auto addedFunc = classA->functions().constLast(); + QCOMPARE(addedFunc->access(), Access::Public); + QCOMPARE(addedFunc->functionType(), AbstractMetaFunction::ConstructorFunction); + QCOMPARE(addedFunc->arguments().size(), 1); + QVERIFY(addedFunc->isUserAdded()); + QVERIFY(addedFunc->isVoid()); +} + +void TestAddFunction::testAddFunctionTagDefaultValues() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'>\n\ + <add-function signature='func()'/>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + // default ctor, default copy ctor and the added function + QCOMPARE(classA->functions().size(), 3); + const auto addedFunc = classA->functions().constLast(); + QCOMPARE(addedFunc->access(), Access::Public); + QCOMPARE(addedFunc->functionType(), AbstractMetaFunction::NormalFunction); + QVERIFY(addedFunc->isUserAdded()); + QVERIFY(addedFunc->isVoid()); +} + +void TestAddFunction::testAddFunctionCodeSnippets() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'>\n\ + <add-function signature='func()'>\n\ + <inject-code class='target' position='end'>Hi!, I am the code.</inject-code>\n\ + </add-function>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->functions().constLast(); + QVERIFY(addedFunc->hasInjectedCode()); +} + +void TestAddFunction::testAddFunctionWithoutParenteses() +{ + static constexpr auto sig1 = "func"_L1; + QString errorMessage; + auto f1 = AddedFunction::createAddedFunction(sig1, voidT, &errorMessage); + QVERIFY2(f1, qPrintable(errorMessage)); + QCOMPARE(f1->name(), u"func"); + QCOMPARE(f1->arguments().size(), 0); + QCOMPARE(f1->isConstant(), false); + + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'>\n\ + <add-function signature='func'>\n\ + <inject-code class='target' position='end'>Hi!, I am the code.</inject-code>\n\ + </add-function>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction(sig1); + QVERIFY(addedFunc); + QVERIFY(addedFunc->hasInjectedCode()); + const auto snips = addedFunc->injectedCodeSnips(TypeSystem::CodeSnipPositionAny, + TypeSystem::TargetLangCode); + QCOMPARE(snips.size(), 1); +} + +void TestAddFunction::testAddFunctionWithDefaultArgs() +{ + static constexpr auto sig1 = "func"_L1; + QString errorMessage; + auto f1 = AddedFunction::createAddedFunction(sig1, voidT, &errorMessage); + QVERIFY2(f1, qPrintable(errorMessage)); + QCOMPARE(f1->name(), u"func"); + QCOMPARE(f1->arguments().size(), 0); + QCOMPARE(f1->isConstant(), false); + + const char cppCode[] = "struct A { };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'>\n\ + <add-function signature='func(int, int)'>\n\ + <modify-argument index='2'>\n\ + <replace-default-expression with='2'/>\n\ + </modify-argument>\n\ + </add-function>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction(sig1); + QVERIFY(addedFunc); + const AbstractMetaArgument &arg = addedFunc->arguments().at(1); + QCOMPARE(arg.defaultValueExpression(), u"2"); +} + +void TestAddFunction::testAddFunctionAtModuleLevel() +{ + const char cppCode[] = "struct A { };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'/>\n\ + <add-function signature='func(int, int)'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + + auto *typeDb = TypeDatabase::instance(); + + AddedFunctionList addedFuncs = typeDb->findGlobalUserFunctions(u"func"_s); + + QCOMPARE(addedFuncs.size(), 1); + + auto &mods = addedFuncs.constFirst()->modifications(); + + QCOMPARE(mods.size(), 1); + QVERIFY(mods.constFirst().isCodeInjection()); + CodeSnip snip = mods.constFirst().snips().constFirst(); + QCOMPARE(snip.code().trimmed(), u"custom_code();"); +} + +void TestAddFunction::testAddFunctionWithVarargs() +{ + QString errorMessage; + auto f1 = AddedFunction::createAddedFunction("func(int,char,...)"_L1, voidT, + &errorMessage); + QVERIFY2(f1, qPrintable(errorMessage)); + QCOMPARE(f1->name(), u"func"); + QCOMPARE(f1->arguments().size(), 3); + QVERIFY(!f1->isConstant()); + + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <primitive-type name='char'/>\n\ + <value-type name='A'>\n\ + <add-function signature='func(int,char,...)'/>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction("func"); + QVERIFY(addedFunc); + const AbstractMetaArgument &arg = addedFunc->arguments().constLast(); + QVERIFY(arg.type().isVarargs()); + QVERIFY(arg.type().typeEntry()->isVarargs()); +} + +void TestAddFunction::testAddStaticFunction() +{ + const char cppCode[] = "struct A { };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'>\n\ + <add-function signature='func(int, int)' static='yes'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction("func"); + QVERIFY(addedFunc); + QVERIFY(addedFunc->isStatic()); +} + +void TestAddFunction::testAddGlobalFunction() +{ + const char cppCode[] = "struct A { };struct B {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'/>\n\ + <add-function signature='globalFunc(int, int)' static='yes'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + <add-function signature='globalFunc2(int, int)' static='yes'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + <value-type name='B'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + const auto globalFuncs = builder->globalFunctions(); + QCOMPARE(globalFuncs.size(), 2); + const auto classB = AbstractMetaClass::findClass(builder->classes(), "B"); + QVERIFY(classB); + QVERIFY(!classB->findFunction("globalFunc")); + QVERIFY(!classB->findFunction("globalFunc2")); + QVERIFY(!globalFuncs[0]->injectedCodeSnips().isEmpty()); + QVERIFY(!globalFuncs[1]->injectedCodeSnips().isEmpty()); +} + +void TestAddFunction::testAddFunctionWithApiVersion() +{ + const char cppCode[] = ""; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <add-function signature='globalFunc(int, int)' static='yes' since='1.3'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + <add-function signature='globalFunc2(int, int)' static='yes' since='0.1'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + true, u"0.1"_s)); + QVERIFY(builder); + const auto globalFuncs = builder->globalFunctions(); + QCOMPARE(globalFuncs.size(), 1); +} + +void TestAddFunction::testModifyAddedFunction() +{ + const char cppCode[] = "class Foo { };\n"; + const char xmlCode[] = R"( +<typesystem package='Package'> + <primitive-type name='float'/> + <primitive-type name='int'/> + <value-type name='Foo'> + <add-function signature='method(float, int)'> + <inject-code class='target' position='beginning'>custom_code();</inject-code> + <modify-argument index='2' rename='varName'> + <replace-default-expression with='0'/> + </modify-argument> + </add-function> + </value-type> +</typesystem> +)"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto foo = AbstractMetaClass::findClass(classes, "Foo"); + const auto method = foo->findFunction("method"); + QVERIFY(method); + QCOMPARE(method->arguments().size(), 2); + const AbstractMetaArgument &arg = method->arguments().at(1); + QCOMPARE(arg.defaultValueExpression(), u"0"); + QCOMPARE(arg.name(), u"varName"); + QCOMPARE(method->argumentName(2), u"varName"); +} + +void TestAddFunction::testAddFunctionOnTypedef() +{ + const char cppCode[] = "template<class T> class Foo { }; typedef Foo<int> FooInt;\n"; + const char xmlCode[] = "\ + <typesystem package='Package'>\n\ + <value-type name='FooInt'>\n\ + <add-function signature='FooInt(PySequence)'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + <add-function signature='method()'>\n\ + <inject-code class='target' position='beginning'>custom_code();</inject-code>\n\ + </add-function>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto foo = AbstractMetaClass::findClass(classes, "FooInt"); + QVERIFY(foo); + QVERIFY(foo->hasNonPrivateConstructor()); + const auto &lst = foo->queryFunctions(FunctionQueryOption::AnyConstructor); + for (const auto &f : lst) + QVERIFY(f->signature().startsWith(f->name())); + QCOMPARE(lst.size(), 2); + const auto method = foo->findFunction("method"); + QVERIFY(method); +} + +void TestAddFunction::testAddFunctionWithTemplateArg() +{ + const char cppCode[] = "template<class T> class Foo { };\n"; + const char xmlCode[] = "\ + <typesystem package='Package'>\n\ + <primitive-type name='int'/>\n\ + <container-type name='Foo' type='list'/>\n\ + <add-function signature='func(Foo<int>)'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + QCOMPARE(builder->globalFunctions().size(), 1); + const auto func = builder->globalFunctions().constFirst(); + const AbstractMetaArgument &arg = func->arguments().constFirst(); + QCOMPARE(arg.type().instantiations().size(), 1); +} + +// Test splitting of <add-function> parameter lists. + +Q_DECLARE_METATYPE(AddedFunctionParser::Argument) + +using Arguments = AddedFunctionParser::Arguments; + +void TestAddFunction::testAddFunctionTypeParser_data() +{ + QTest::addColumn<QString>("parameterList"); + QTest::addColumn<Arguments>("expected"); + + QTest::newRow("empty") + << QString() << Arguments{}; + + QTest::newRow("1-arg") + << QString::fromLatin1("int @a@=42") + << Arguments{{u"int"_s, u"a"_s, u"42"_s}}; + + QTest::newRow("2-args") + << QString::fromLatin1("double @d@, int @a@=42") + << Arguments{{u"double"_s, u"d"_s, {}}, + {u"int"_s, u"a"_s, u"42"_s}}; + + QTest::newRow("template-var_args") + << QString::fromLatin1("const QList<X,Y> &@list@ = QList<X,Y>{1,2}, int @b@=5, ...") + << Arguments{{u"const QList<X,Y> &"_s, u"list"_s, u"QList<X,Y>{1,2}"_s}, + {u"int"_s, u"b"_s, u"5"_s}, + {u"..."_s, {}, {}}}; +} + +void TestAddFunction::testAddFunctionTypeParser() +{ + + QFETCH(QString, parameterList); + QFETCH(Arguments, expected); + + const auto actual = AddedFunctionParser::splitParameters(parameterList); + QCOMPARE(actual, expected); +} + +QTEST_APPLESS_MAIN(TestAddFunction) diff --git a/sources/shiboken6/ApiExtractor/tests/testaddfunction.h b/sources/shiboken6/ApiExtractor/tests/testaddfunction.h new file mode 100644 index 000000000..77339609f --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testaddfunction.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTADDFUNCTION_H +#define TESTADDFUNCTION_H +#include <QtCore/QObject> + +class TestAddFunction : public QObject +{ + Q_OBJECT +private slots: + void testParsingFuncNameAndConstness(); + void testAddFunction(); + void testAddFunctionConstructor(); + void testAddFunctionTagDefaultValues(); + void testAddFunctionCodeSnippets(); + void testAddFunctionWithoutParenteses(); + void testAddFunctionWithDefaultArgs(); + void testAddFunctionAtModuleLevel(); + void testAddFunctionWithVarargs(); + void testAddStaticFunction(); + void testAddGlobalFunction(); + void testAddFunctionWithApiVersion(); + void testModifyAddedFunction(); + void testAddFunctionOnTypedef(); + void testAddFunctionWithTemplateArg(); + void testAddFunctionTypeParser_data(); + void testAddFunctionTypeParser(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp b/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp new file mode 100644 index 000000000..6e1820bed --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testarrayargument.cpp @@ -0,0 +1,155 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testarrayargument.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetaenum.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <primitivetypeentry.h> +#include <parser/enumvalue.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestArrayArgument::testArrayArgumentWithSizeDefinedByInteger() +{ + const char cppCode[] = "\ + struct A {\n\ + enum SomeEnum { Value0, Value1, NValues };\n\ + void method(double[3]);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='double'/>\n\ + <object-type name='A'>\n\ + <enum-type name='SomeEnum'/>\n\ + </object-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(!builder.isNull()); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + + const AbstractMetaArgument &arg = classA->functions().constLast()->arguments().constFirst(); + QVERIFY(arg.type().isArray()); + QCOMPARE(arg.type().arrayElementCount(), 3); + QCOMPARE(arg.type().arrayElementType()->name(), u"double"); +} + +static QString functionMinimalSignature(const AbstractMetaClassCPtr &c, const QString &name) +{ + const auto f = c->findFunction(name); + return f ? f->minimalSignature() : QString(); +} + +void TestArrayArgument::testArraySignature() +{ + const char cppCode[] ="\ + struct A {\n\ + void mi1(int arg[5]);\n\ + void mi1c(const int arg[5]);\n\ + void mi1cu(const int arg[]);\n\ + void mc1cu(const char arg[]);\n\ + void mc1cup(const char *arg[]);\n\ + void muc2(unsigned char *arg[2][3]);\n\ + void mc2c(const char *arg[5][6]);\n\ + void mc2cu(const char arg[][2]);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='char'/>\n\ + <primitive-type name='unsigned char'/>\n\ + <primitive-type name='int'/>\n\ + <object-type name='A'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(!builder.isNull()); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QCOMPARE(functionMinimalSignature(classA, u"mi1"_s), + u"mi1(int[5])"); + QCOMPARE(functionMinimalSignature(classA, u"mi1c"_s), + u"mi1c(const int[5])"); + QCOMPARE(functionMinimalSignature(classA, u"mi1cu"_s), + u"mi1cu(const int[])"); + QCOMPARE(functionMinimalSignature(classA, u"mc1cu"_s), + u"mc1cu(const char*)"); + QCOMPARE(functionMinimalSignature(classA, u"mc1cup"_s), + u"mc1cup(const char*[])"); + QCOMPARE(functionMinimalSignature(classA, u"muc2"_s), + u"muc2(unsigned char*[2][3])"); + QCOMPARE(functionMinimalSignature(classA, u"mc2c"_s), + u"mc2c(const char*[5][6])"); + QCOMPARE(functionMinimalSignature(classA, u"mc2cu"_s), + u"mc2cu(const char[][2])"); +} + +void TestArrayArgument::testArrayArgumentWithSizeDefinedByEnumValue() +{ + const char cppCode[] = "\ + struct A {\n\ + enum SomeEnum { Value0, Value1, NValues };\n\ + void method(double[NValues]);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='double'/>\n\ + <object-type name='A'>\n\ + <enum-type name='SomeEnum'/>\n\ + </object-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(!builder.isNull()); + AbstractMetaClassPtr classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + + auto someEnum = classA->findEnum(u"SomeEnum"_s); + QVERIFY(someEnum.has_value()); + auto nvalues = classA->findEnumValue(u"NValues"_s); + QVERIFY(nvalues.has_value()); + + const AbstractMetaArgument &arg = classA->functions().constLast()->arguments().constFirst(); + QVERIFY(arg.type().isArray()); + QCOMPARE(arg.type().arrayElementCount(), nvalues->value().value()); + QCOMPARE(arg.type().arrayElementType()->name(), u"double"); +}; + +void TestArrayArgument::testArrayArgumentWithSizeDefinedByEnumValueFromGlobalEnum() +{ + const char cppCode[] = "\ + enum SomeEnum { Value0, Value1, NValues };\n\ + struct A {\n\ + void method(double[NValues]);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='double'/>\n\ + <enum-type name='SomeEnum'/>\n\ + <object-type name='A'>\n\ + </object-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + + AbstractMetaEnum someEnum = builder->globalEnums().constFirst(); + auto nvalues = someEnum.findEnumValue(u"NValues"); + QVERIFY(nvalues.has_value()); + + const AbstractMetaArgument &arg = classA->functions().constLast()->arguments().constFirst(); + QVERIFY(arg.type().isArray()); + QCOMPARE(arg.type().arrayElementCount(), nvalues->value().value()); + QCOMPARE(arg.type().arrayElementType()->name(), u"double"); +}; + +QTEST_APPLESS_MAIN(TestArrayArgument) diff --git a/sources/shiboken6/ApiExtractor/tests/testarrayargument.h b/sources/shiboken6/ApiExtractor/tests/testarrayargument.h new file mode 100644 index 000000000..75ef0f792 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testarrayargument.h @@ -0,0 +1,18 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTARRAYARGUMENT_H +#define TESTARRAYARGUMENT_H +#include <QtCore/QObject> + +class TestArrayArgument : public QObject +{ + Q_OBJECT +private slots: + void testArrayArgumentWithSizeDefinedByInteger(); + void testArraySignature(); + void testArrayArgumentWithSizeDefinedByEnumValue(); + void testArrayArgumentWithSizeDefinedByEnumValueFromGlobalEnum(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp new file mode 100644 index 000000000..4829e6c33 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.cpp @@ -0,0 +1,164 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testcodeinjection.h" +#include "testutil.h" +#include <abstractmetalang.h> +#include <codesnip.h> +#include <modifications.h> +#include <textstream.h> +#include <complextypeentry.h> +#include <valuetypeentry.h> + +#include <qtcompat.h> + +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestCodeInjections::testReadFile_data() +{ + QTest::addColumn<QString>("filePath"); + QTest::addColumn<QString>("snippet"); + QTest::addColumn<QString>("expected"); + + QTest::newRow("utf8") + << QString::fromLatin1(":/utf8code.txt") + << QString() + << QString::fromUtf8("\xC3\xA1\xC3\xA9\xC3\xAD\xC3\xB3\xC3\xBA"); + + QTest::newRow("snippet") + << QString::fromLatin1(":/injectedcode.txt") + << QString::fromLatin1("label") + << QString::fromLatin1("code line"); +} + +void TestCodeInjections::testReadFile() +{ + QFETCH(QString, filePath); + QFETCH(QString, snippet); + QFETCH(QString, expected); + + const char cppCode[] = "struct A {};\n"; + int argc = 0; + char *argv[] = {nullptr}; + QCoreApplication app(argc, argv); + + QString attribute = u"file='"_s + filePath + u'\''; + if (!snippet.isEmpty()) + attribute += u" snippet='"_s + snippet + u'\''; + + QString xmlCode = u"\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'>\n\ + <conversion-rule class='target' "_s + attribute + u"/>\n\ + <inject-code class='target' "_s + attribute + u"/>\n\ + <value-type name='B'/>\n\ + </value-type>\n\ + </typesystem>\n"_s; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode.toLocal8Bit().constData())); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QCOMPARE(classA->typeEntry()->codeSnips().size(), 1); + QString code = classA->typeEntry()->codeSnips().constFirst().code(); + QVERIFY(code.indexOf(expected) != -1); + QVERIFY(classA->typeEntry()->isValue()); + auto vte = std::static_pointer_cast<const ValueTypeEntry>(classA->typeEntry()); + code = vte->targetConversionRule(); + QVERIFY(code.indexOf(expected) != -1); +} + +void TestCodeInjections::testInjectWithValidApiVersion() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'>\n\ + <inject-code class='target' since='1.0'>\n\ + test Inject code\n\ + </inject-code>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + true, u"1.0"_s)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QCOMPARE(classA->typeEntry()->codeSnips().size(), 1); +} + +void TestCodeInjections::testInjectWithInvalidApiVersion() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'>\n\ + <inject-code class='target' since='1.0'>\n\ + test Inject code\n\ + </inject-code>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + true, u"0.1"_s)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QCOMPARE(classA->typeEntry()->codeSnips().size(), 0); +} + +void TestCodeInjections::testTextStream() +{ + StringStream str(TextStream::Language::Cpp); + str << "void foo(int a, int b) {\n" << indent + << "if (a == b)\n" << indent << "return a;\n" << outdent + << "#if Q_OS_WIN\nprint()\n#endif\nreturn a + b;\n" << outdent + << "}\n\n// A table\n|" + << AlignedField("bla", 40, QTextStream::AlignRight) << "|\n|" + << AlignedField("bla", 40, QTextStream::AlignLeft) << "|\n|" + << AlignedField(QString(), 40, QTextStream::AlignLeft) << "|\n"; + str << "\n2nd table\n|" << AlignedField("bla", 3, QTextStream::AlignLeft) + << '|' << AlignedField(QString{}, 0, QTextStream::AlignLeft) << "|\n"; + +constexpr auto expected = R"(void foo(int a, int b) { + if (a == b) + return a; +#if Q_OS_WIN + print() +#endif + return a + b; +} + +// A table +| bla| +|bla | +| | + +2nd table +|bla|| +)"_L1; + + QCOMPARE(str.toString(), expected); +} + +void TestCodeInjections::testTextStreamRst() +{ + // Test that sphinx error: "Inline strong start-string without end-string." + // is avoided, that is, characters following a formatting end are escaped. + + StringStream str; + str << rstBold << "QObject" << rstBoldOff << "'s properties..." + << rstItalic << "some italic" << rstItalicOff << " followed by space."; + + static const char16_t expected[] = + uR"(**QObject**\'s properties...*some italic* followed by space.)"; + + QCOMPARE(str.toString(), expected); +} + +QTEST_APPLESS_MAIN(TestCodeInjections) diff --git a/sources/shiboken6/ApiExtractor/tests/testcodeinjection.h b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.h new file mode 100644 index 000000000..a164ea36e --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.h @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTCODEINJECTIONS_H +#define TESTCODEINJECTIONS_H + +#include <QtCore/QObject> + +class AbstractMetaBuilder; + +class TestCodeInjections : public QObject +{ + Q_OBJECT +private slots: + void testReadFile_data(); + void testReadFile(); + void testInjectWithValidApiVersion(); + void testInjectWithInvalidApiVersion(); + void testTextStream(); + void testTextStreamRst(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testcodeinjection.qrc b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.qrc new file mode 100644 index 000000000..fd7616bd2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testcodeinjection.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource> + <file>utf8code.txt</file> + <file>injectedcode.txt</file> + </qresource> +</RCC> diff --git a/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp b/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp new file mode 100644 index 000000000..0bb72b3c1 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testcontainer.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testcontainer.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <complextypeentry.h> +#include <containertypeentry.h> + +void TestContainer::testContainerType() +{ + const char cppCode[] = "\ + namespace std {\n\ + template<class T>\n\ + class list {\n\ + T get(int x) { return 0; }\n\ + };\n\ + }\n\ + class A : public std::list<int> {\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <namespace-type name='std' generate='no' />\n\ + <container-type name='std::list' type='list' />\n\ + <object-type name='A'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, true)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + //search for class A + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + auto baseContainer = classA->typeEntry()->baseContainerType(); + QVERIFY(baseContainer); + QCOMPARE(reinterpret_cast<const ContainerTypeEntry*>(baseContainer.get())->containerKind(), + ContainerTypeEntry::ListContainer); +} + +void TestContainer::testListOfValueType() +{ + const char cppCode[] = "\ + namespace std {\n\ + template<class T>\n\ + class list {\n\ + T get(int x) { return 0; }\n\ + };\n\ + }\n\ + class ValueType {};\n\ + class A : public std::list<ValueType> {\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <namespace-type name='std' generate='no'/>\n\ + <container-type name='std::list' type='list'/>\n\ + <value-type name='ValueType'/>\n\ + <value-type name='A'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, true)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 3); + + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->templateBaseClassInstantiations().size(), 1); + const AbstractMetaType templateInstanceType = + classA->templateBaseClassInstantiations().constFirst(); + + QCOMPARE(templateInstanceType.indirections(), 0); + QVERIFY(!templateInstanceType.typeEntry()->isObject()); + QVERIFY(templateInstanceType.typeEntry()->isValue()); + QCOMPARE(templateInstanceType.referenceType(), NoReference); + QVERIFY(!templateInstanceType.isObject()); + QVERIFY(!templateInstanceType.isValuePointer()); + QVERIFY(templateInstanceType.isValue()); +} + +QTEST_APPLESS_MAIN(TestContainer) + diff --git a/sources/shiboken6/ApiExtractor/tests/testcontainer.h b/sources/shiboken6/ApiExtractor/tests/testcontainer.h new file mode 100644 index 000000000..3fd23c3f0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testcontainer.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTCONTAINER_H +#define TESTCONTAINER_H +#include <QtCore/QObject> + +class TestContainer : public QObject +{ + Q_OBJECT +private slots: + void testContainerType(); + void testListOfValueType(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp b/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp new file mode 100644 index 000000000..8f2b277af --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testconversionoperator.cpp @@ -0,0 +1,178 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testconversionoperator.h" +#include "testutil.h" +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <typesystem.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestConversionOperator::testConversionOperator() +{ + const char cppCode[] = "\ + struct A {\n\ + };\n\ + struct B {\n\ + operator A() const;\n\ + };\n\ + struct C {\n\ + operator A() const;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + <value-type name='C'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); + QVERIFY(classA); + QVERIFY(classB); + QVERIFY(classC); + QCOMPARE(classA->functions().size(), 2); + QCOMPARE(classB->functions().size(), 3); + QCOMPARE(classC->functions().size(), 3); + QCOMPARE(classA->externalConversionOperators().size(), 2); + + AbstractMetaFunctionCPtr convOp; + for (const auto &func : classB->functions()) { + if (func->isConversionOperator()) { + convOp = func; + break; + } + } + QVERIFY(convOp); + QVERIFY(classA->externalConversionOperators().contains(convOp)); +} + +void TestConversionOperator::testConversionOperatorOfDiscardedClass() +{ + const char cppCode[] = "\ + struct A {\n\ + };\n\ + struct B {\n\ + operator A() const;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A' />\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->externalConversionOperators().size(), 0); +} + +void TestConversionOperator::testRemovedConversionOperator() +{ + const char cppCode[] = "\ + struct A {\n\ + };\n\ + struct B {\n\ + operator A() const;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A' />\n\ + <value-type name='B'>\n\ + <modify-function signature='operator A() const' remove='all'/>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classA); + QVERIFY(classB); + QCOMPARE(classA->functions().size(), 2); + QCOMPARE(classB->functions().size(), 3); + QCOMPARE(classA->externalConversionOperators().size(), 0); + QCOMPARE(classA->implicitConversions().size(), 0); +} + +void TestConversionOperator::testConversionOperatorReturningReference() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + operator A&() const;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classA); + QVERIFY(classB); + QCOMPARE(classA->functions().size(), 2); + QCOMPARE(classB->functions().size(), 3); + QCOMPARE(classA->externalConversionOperators().size(), 1); + QCOMPARE(classA->externalConversionOperators().constFirst()->type().cppSignature(), + u"A"); + QCOMPARE(classA->externalConversionOperators().constFirst()->ownerClass()->name(), + u"B"); + QCOMPARE(classA->implicitConversions().size(), 1); + QCOMPARE(classA->implicitConversions().constFirst()->type().cppSignature(), + u"A"); + QCOMPARE(classA->implicitConversions().constFirst()->ownerClass()->name(), + u"B"); +} + +void TestConversionOperator::testConversionOperatorReturningConstReference() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + operator const A&() const;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classA); + QVERIFY(classB); + QCOMPARE(classA->functions().size(), 2); + QCOMPARE(classB->functions().size(), 3); + QCOMPARE(classA->externalConversionOperators().size(), 1); + QCOMPARE(classA->externalConversionOperators().constFirst()->type().cppSignature(), + u"A"_s); + QCOMPARE(classA->externalConversionOperators().constFirst()->ownerClass()->name(), + u"B"_s); + QCOMPARE(classA->implicitConversions().size(), 1); + QCOMPARE(classA->implicitConversions().constFirst()->type().cppSignature(), + u"A"_s); + QCOMPARE(classA->implicitConversions().constFirst()->ownerClass()->name(), + u"B"_s); +} + +QTEST_APPLESS_MAIN(TestConversionOperator) diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionoperator.h b/sources/shiboken6/ApiExtractor/tests/testconversionoperator.h new file mode 100644 index 000000000..68288d240 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testconversionoperator.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTCONVERSIONOPERATOR_H +#define TESTCONVERSIONOPERATOR_H +#include <QtCore/QObject> + +class TestConversionOperator : public QObject +{ + Q_OBJECT +private slots: + void testConversionOperator(); + void testConversionOperatorOfDiscardedClass(); + void testRemovedConversionOperator(); + void testConversionOperatorReturningReference(); + void testConversionOperatorReturningConstReference(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp new file mode 100644 index 000000000..b5efd92a6 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.cpp @@ -0,0 +1,240 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testconversionruletag.h" +#include "testutil.h" +#include <abstractmetalang.h> +#include <complextypeentry.h> +#include <customconversion.h> +#include <primitivetypeentry.h> +#include <valuetypeentry.h> + +#include <qtcompat.h> + +#include <QtCore/QFile> +#include <QtCore/QTemporaryFile> +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestConversionRuleTag::testConversionRuleTagWithFile() +{ + // FIXME PYSIDE7 remove + // temp file used later + constexpr auto conversionData = "Hi! I'm a conversion rule."_L1; + QTemporaryFile file; + QVERIFY(file.open()); + QCOMPARE(file.write(conversionData.constData()), conversionData.size()); + file.close(); + + const char cppCode[] = "struct A {};\n"; + QString xmlCode = u"\ + <typesystem package='Foo'>\n\ + <value-type name='A'>\n\ + <conversion-rule class='target' file='"_s + file.fileName() + u"'/>\n\ + </value-type>\n\ + </typesystem>\n"_s; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode.toLocal8Bit().data())); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto typeEntry = classA->typeEntry(); + QVERIFY(typeEntry->isValue()); + auto vte = std::static_pointer_cast<const ValueTypeEntry>(typeEntry); + QVERIFY(vte->hasTargetConversionRule()); + QCOMPARE(vte->targetConversionRule(), conversionData); +} + +void TestConversionRuleTag::testConversionRuleTagReplace() +{ + const char cppCode[] = "\ + struct A {\n\ + A();\n\ + A(const char*, int);\n\ + };\n\ + struct B {\n\ + A createA();\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <primitive-type name='char'/>\n\ + <primitive-type name='A'>\n\ + <conversion-rule>\n\ + <native-to-target>\n\ + DoThis();\n\ + return ConvertFromCppToPython(%IN);\n\ + </native-to-target>\n\ + <target-to-native>\n\ + <add-conversion type='TargetNone' check='%IN == Target_None'>\n\ + DoThat();\n\ + DoSomething();\n\ + %OUT = A();\n\ + </add-conversion>\n\ + <add-conversion type='B' check='CheckIfInputObjectIsB(%IN)'>\n\ + %OUT = %IN.createA();\n\ + </add-conversion>\n\ + <add-conversion type='String' check='String_Check(%IN)'>\n\ + %OUT = new A(String_AsString(%IN), String_GetSize(%IN));\n\ + </add-conversion>\n\ + </target-to-native>\n\ + </conversion-rule>\n\ + </primitive-type>\n\ + <value-type name='B'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + auto *typeDb = TypeDatabase::instance(); + auto typeA = typeDb->findPrimitiveType(u"A"_s); + QVERIFY(typeA); + + QVERIFY(typeA->hasCustomConversion()); + auto conversion = typeA->customConversion(); + + QCOMPARE(typeA, conversion->ownerType()); + QCOMPARE(conversion->nativeToTargetConversion().simplified(), + u"DoThis(); return ConvertFromCppToPython(%IN);"); + + QVERIFY(conversion->replaceOriginalTargetToNativeConversions()); + QVERIFY(conversion->hasTargetToNativeConversions()); + QCOMPARE(conversion->targetToNativeConversions().size(), 3); + + QVERIFY(!conversion->targetToNativeConversions().isEmpty()); + auto toNative = conversion->targetToNativeConversions().at(0); + QCOMPARE(toNative.sourceTypeName(), u"TargetNone"); + QVERIFY(toNative.isCustomType()); + QCOMPARE(toNative.sourceType(), nullptr); + QCOMPARE(toNative.sourceTypeCheck(), u"%IN == Target_None"); + QCOMPARE(toNative.conversion().simplified(), + u"DoThat(); DoSomething(); %OUT = A();"); + + QVERIFY(conversion->targetToNativeConversions().size() > 1); + toNative = conversion->targetToNativeConversions().at(1); + QCOMPARE(toNative.sourceTypeName(), u"B"); + QVERIFY(!toNative.isCustomType()); + auto typeB = typeDb->findType(u"B"_s); + QVERIFY(typeB); + QCOMPARE(toNative.sourceType(), typeB); + QCOMPARE(toNative.sourceTypeCheck(), u"CheckIfInputObjectIsB(%IN)"); + QCOMPARE(toNative.conversion().trimmed(), u"%OUT = %IN.createA();"); + + QVERIFY(conversion->targetToNativeConversions().size() > 2); + toNative = conversion->targetToNativeConversions().at(2); + QCOMPARE(toNative.sourceTypeName(), u"String"); + QVERIFY(toNative.isCustomType()); + QCOMPARE(toNative.sourceType(), nullptr); + QCOMPARE(toNative.sourceTypeCheck(), u"String_Check(%IN)"); + QCOMPARE(toNative.conversion().trimmed(), + u"%OUT = new A(String_AsString(%IN), String_GetSize(%IN));"); +} + +void TestConversionRuleTag::testConversionRuleTagAdd() +{ + const char cppCode[] = "\ + struct Date {\n\ + Date();\n\ + Date(int, int, int);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <value-type name='Date'>\n\ + <conversion-rule>\n\ + <target-to-native replace='no'>\n\ + <add-conversion type='TargetDate' check='TargetDate_Check(%IN)'>\n\ +if (!TargetDateTimeAPI) TargetDateTime_IMPORT;\n\ +%OUT = new Date(TargetDate_Day(%IN), TargetDate_Month(%IN), TargetDate_Year(%IN));\n\ + </add-conversion>\n\ + </target-to-native>\n\ + </conversion-rule>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "Date"); + QVERIFY(classA); + + QVERIFY(classA->typeEntry()->isValue()); + auto vte = std::static_pointer_cast<const ValueTypeEntry>(classA->typeEntry()); + QVERIFY(vte->hasCustomConversion()); + auto conversion = vte->customConversion(); + + QCOMPARE(conversion->nativeToTargetConversion(), QString()); + + QVERIFY(!conversion->replaceOriginalTargetToNativeConversions()); + QVERIFY(conversion->hasTargetToNativeConversions()); + QCOMPARE(conversion->targetToNativeConversions().size(), 1); + + QVERIFY(!conversion->targetToNativeConversions().isEmpty()); + const auto &toNative = conversion->targetToNativeConversions().constFirst(); + QCOMPARE(toNative.sourceTypeName(), u"TargetDate"); + QVERIFY(toNative.isCustomType()); + QCOMPARE(toNative.sourceType(), nullptr); + QCOMPARE(toNative.sourceTypeCheck(), u"TargetDate_Check(%IN)"); + QCOMPARE(toNative.conversion().trimmed(), + uR"(if (!TargetDateTimeAPI) TargetDateTime_IMPORT; +%OUT = new Date(TargetDate_Day(%IN), TargetDate_Month(%IN), TargetDate_Year(%IN));)"); +} + +void TestConversionRuleTag::testConversionRuleTagWithInsertTemplate() +{ + const char cppCode[] = "struct A {};"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <!-- single line -->\n\ + <template name='native_to_target'>return ConvertFromCppToPython(%IN);</template>\n\ + <!-- multi-line -->\n\ + <template name='target_to_native'>\n\ +%OUT = %IN.createA();\n\ + </template>\n\ + <primitive-type name='A'>\n\ + <conversion-rule>\n\ + <native-to-target>\n\ + <insert-template name='native_to_target'/>\n\ + </native-to-target>\n\ + <target-to-native>\n\ + <add-conversion type='TargetType'>\n\ + <insert-template name='target_to_native'/>\n\ + </add-conversion>\n\ + </target-to-native>\n\ + </conversion-rule>\n\ + </primitive-type>\n\ + </typesystem>\n"; + + const char nativeToTargetExpected[] = + "// TEMPLATE - native_to_target - START\n" + "return ConvertFromCppToPython(%IN);\n" + "// TEMPLATE - native_to_target - END"; + + const char targetToNativeExpected[] = + "// TEMPLATE - target_to_native - START\n" + "%OUT = %IN.createA();\n" + "// TEMPLATE - target_to_native - END"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + auto *typeDb = TypeDatabase::instance(); + auto typeA = typeDb->findPrimitiveType(u"A"_s); + QVERIFY(typeA); + + QVERIFY(typeA->hasCustomConversion()); + auto conversion = typeA->customConversion(); + + QCOMPARE(typeA, conversion->ownerType()); + QCOMPARE(conversion->nativeToTargetConversion().trimmed(), + QLatin1StringView(nativeToTargetExpected)); + + QVERIFY(conversion->hasTargetToNativeConversions()); + QCOMPARE(conversion->targetToNativeConversions().size(), 1); + + QVERIFY(!conversion->targetToNativeConversions().isEmpty()); + const auto &toNative = conversion->targetToNativeConversions().constFirst(); + QCOMPARE(toNative.conversion().trimmed(), + QLatin1StringView(targetToNativeExpected)); +} + +QTEST_APPLESS_MAIN(TestConversionRuleTag) diff --git a/sources/shiboken6/ApiExtractor/tests/testconversionruletag.h b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.h new file mode 100644 index 000000000..64d496cc3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testconversionruletag.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTCONVERSIONRULE_H +#define TESTCONVERSIONRULE_H + +#include <QtCore/QObject> + +class TestConversionRuleTag : public QObject +{ + Q_OBJECT +private slots: + void testConversionRuleTagWithFile(); + void testConversionRuleTagReplace(); + void testConversionRuleTagAdd(); + void testConversionRuleTagWithInsertTemplate(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp b/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp new file mode 100644 index 000000000..c3a3ebef0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testctorinformation.cpp @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testctorinformation.h" +#include "abstractmetabuilder.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetalang.h> +#include <typesystem.h> + +void TestCtorInformation::testCtorIsPrivate() +{ + const char cppCode[] = "class Control { public: Control() {} };\n\ + class Subject { private: Subject() {} };\n\ + class CtorLess { };\n"; + const char xmlCode[] = "<typesystem package='Foo'>\n\ + <value-type name='Control'/>\n\ + <object-type name='Subject'/>\n\ + <value-type name='CtorLess'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 3); + auto klass = AbstractMetaClass::findClass(classes, "Control"); + QVERIFY(klass); + QVERIFY(klass->hasNonPrivateConstructor()); + klass = AbstractMetaClass::findClass(classes, "Subject"); + QVERIFY(klass); + QVERIFY(!klass->hasNonPrivateConstructor()); + klass = AbstractMetaClass::findClass(classes, "CtorLess"); + QVERIFY(klass); + QVERIFY(klass->hasNonPrivateConstructor()); +} + +void TestCtorInformation::testHasNonPrivateCtor() +{ + const char cppCode[] = "template<typename T>\n\ + struct Base { Base(double) {} };\n\ + typedef Base<int> Derived;\n"; + const char xmlCode[] = "<typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <primitive-type name='double'/>\n\ + <object-type name='Base' generate='no'/>\n\ + <object-type name='Derived'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto base = AbstractMetaClass::findClass(classes, "Base"); + QCOMPARE(base->hasNonPrivateConstructor(), true); + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); + QCOMPARE(derived->hasNonPrivateConstructor(), true); +} + +QTEST_APPLESS_MAIN(TestCtorInformation) diff --git a/sources/shiboken6/ApiExtractor/tests/testctorinformation.h b/sources/shiboken6/ApiExtractor/tests/testctorinformation.h new file mode 100644 index 000000000..58f1648e4 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testctorinformation.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTCTORINFORMATION_H +#define TESTCTORINFORMATION_H + +#include <QtCore/QObject> + +class AbstractMetaBuilder; + +class TestCtorInformation: public QObject +{ + Q_OBJECT +private slots: + void testCtorIsPrivate(); + void testHasNonPrivateCtor(); +}; + +#endif // TESTCTORINFORMATION_H diff --git a/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp b/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp new file mode 100644 index 000000000..16f50e69d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.cpp @@ -0,0 +1,228 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testdroptypeentries.h" +#include "testutil.h" +#include <abstractmetaenum.h> +#include <abstractmetalang.h> +#include <typesystem.h> +#include <conditionalstreamreader.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +static const char cppCode[] = "\ + struct ValueA {};\n\ + struct ValueB {};\n\ + struct ObjectA {};\n\ + struct ObjectB {};\n\ + namespace NamespaceA {\n\ + struct InnerClassA {};\n\ + namespace InnerNamespaceA {}\n\ + }\n\ + namespace NamespaceB {}\n\ + enum EnumA { Value0 };\n\ + enum EnumB { Value1 };\n\ + void funcA();\n\ + void funcB();\n"; + +static const char xmlCode[] = "\ +<typesystem package='Foo'>\n\ + <value-type name='ValueA'/>\n\ + <value-type name='ValueB'/>\n\ + <object-type name='ObjectA'/>\n\ + <object-type name='ObjectB'/>\n\ + <namespace-type name='NamespaceA'>\n\ + <value-type name='InnerClassA'/>\n\ + <namespace-type name='InnerNamespaceA'/>\n\ + </namespace-type>\n\ + <namespace-type name='NamespaceB'/>\n\ + <enum-type name='EnumA'/>\n\ + <enum-type name='EnumB'/>\n\ + <function signature='funcA()'/>\n\ + <function signature='funcB()'/>\n\ +</typesystem>\n"; + +void TestDropTypeEntries::testDropEntries() +{ + const QStringList droppedEntries{u"Foo.ValueB"_s, + u"ObjectB"_s, // Check whether module can be omitted + u"Foo.NamespaceA.InnerClassA"_s, + u"Foo.NamespaceB"_s, u"Foo.EnumB"_s, + u"Foo.funcB()"_s, + u"Foo.NamespaceA.InnerNamespaceA"_s}; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false, + QString(), droppedEntries)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QVERIFY(AbstractMetaClass::findClass(classes, "ValueA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "ValueB")); + QVERIFY(AbstractMetaClass::findClass(classes, "ObjectA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "ObjectB")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "NamespaceA::InnerClassA")); + QVERIFY(!AbstractMetaClass::findClass(classes, "NamespaceB")); + + AbstractMetaEnumList globalEnums = builder->globalEnums(); + QCOMPARE(globalEnums.size(), 1); + QCOMPARE(globalEnums.constFirst().name(), u"EnumA"); + + auto *td = TypeDatabase::instance(); + QVERIFY(td->findType(u"funcA"_s)); + QVERIFY(!td->findType(u"funcB"_s)); +} + +void TestDropTypeEntries::testDontDropEntries() +{ + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QVERIFY(AbstractMetaClass::findClass(classes, "ValueA")); + QVERIFY(AbstractMetaClass::findClass(classes, "ValueB")); + QVERIFY(AbstractMetaClass::findClass(classes, "ObjectA")); + QVERIFY(AbstractMetaClass::findClass(classes, "ObjectB")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceA")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceA::InnerClassA")); + QVERIFY(AbstractMetaClass::findClass(classes, "NamespaceB")); + + QCOMPARE(builder->globalEnums().size(), 2); + + auto *td = TypeDatabase::instance(); + QVERIFY(td->findType(u"funcA"_s)); + QVERIFY(td->findType(u"funcB"_s)); +} + +static const char cppCode2[] = "\ + struct ValueA {\n\ + void func();\n\ + };\n"; + +static const char xmlCode2[] = R"( +<typesystem package='Foo'> + <value-type name='ValueA'> + <modify-function signature='func()' remove='all'/> + </value-type> +</typesystem> +)"; + +void TestDropTypeEntries::testDropEntryWithChildTags() +{ + QStringList droppedEntries(u"Foo.ValueA"_s); + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode2, xmlCode2, false, + QString(), droppedEntries)); + QVERIFY(builder); + QVERIFY(!AbstractMetaClass::findClass(builder->classes(), "ValueA")); +} + + +void TestDropTypeEntries::testDontDropEntryWithChildTags() +{ + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode2, xmlCode2, false)); + QVERIFY(builder); + QVERIFY(AbstractMetaClass::findClass(builder->classes(), "ValueA")); +} + +void TestDropTypeEntries::testConditionalParsing_data() +{ + const QString xml = R"(<?xml version="1.0" encoding="UTF-8"?> +<root> + <tag1>text</tag1> + <?if keyword1?> + <tag2>text</tag2> + <?if keyword2?> + <tag3>text</tag3> + <?endif?> + <?if keyword1 !keyword2?> + <tag4>text</tag4> + <?endif?> + <?endif?> + <tag5>text</tag5> + <?if !keyword99?> <!-- Exclusion only --> + <tag6>text</tag6> + <?endif?> +</root>)"_L1; + + constexpr auto root = "root"_L1; + constexpr auto tag1 = "tag1"_L1; + constexpr auto tag2 = "tag2"_L1; + constexpr auto tag3 = "tag3"_L1; + constexpr auto tag4 = "tag4"_L1; + constexpr auto tag5 = "tag5"_L1; + constexpr auto tag6 = "tag6"_L1; + constexpr auto keyword1 = "keyword1"_L1; + constexpr auto keyword2 = "keyword2"_L1; + + QTest::addColumn<QString>("xml"); + QTest::addColumn<QStringList>("keywords"); + QTest::addColumn<QStringList>("expectedTags"); + + QTest::newRow("no-keywords") + << xml << QStringList{} << QStringList{root, tag1, tag5, tag6}; + + QTest::newRow("skip-nested-condition") + << xml << QStringList{keyword1} + << QStringList{root, tag1, tag2, tag4, tag5, tag6}; + + QTest::newRow("both/check-not") + << xml << QStringList{keyword1, keyword2} + << QStringList{root, tag1, tag2, tag3, tag5, tag6}; +} + +// Parse XML and return a list of tags encountered +static QStringList parseXml(const QString &xml, const QStringList &keywords) +{ + QStringList tags; + ConditionalStreamReader reader(xml); + reader.setConditions(keywords); + while (!reader.atEnd()) { + auto t = reader.readNext(); + switch (t) { + case QXmlStreamReader::StartElement: + tags.append(reader.name().toString()); + break; + default: + break; + } + } + return tags; +} + +void TestDropTypeEntries::testConditionalParsing() +{ + QFETCH(QString, xml); + QFETCH(QStringList, keywords); + QFETCH(QStringList, expectedTags); + + const QStringList actualTags = parseXml(xml, keywords); + QCOMPARE(actualTags, expectedTags); +} + +void TestDropTypeEntries::testEntityParsing() +{ + const QString xml = R"(<?xml version="1.0" encoding="UTF-8"?> +<root> + <?entity testentity word1 word2?> + <text>bla &testentity;</text> +</root>)"_L1; + + QString actual; + ConditionalStreamReader reader(xml); + while (!reader.atEnd()) { + auto t = reader.readNext(); + switch (t) { + case QXmlStreamReader::Characters: + actual.append(reader.text()); + default: + break; + } + } + QVERIFY2(!reader.hasError(), qPrintable(reader.errorString())); + QCOMPARE(actual.trimmed(), u"bla word1 word2"); +} + +QTEST_APPLESS_MAIN(TestDropTypeEntries) diff --git a/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.h b/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.h new file mode 100644 index 000000000..98717bd21 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testdroptypeentries.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTDROPTYPEENTRIES_H +#define TESTDROPTYPEENTRIES_H + +#include <QtCore/QObject> + +class TestDropTypeEntries : public QObject +{ + Q_OBJECT + private slots: + void testDropEntries(); + void testDontDropEntries(); + void testDropEntryWithChildTags(); + void testDontDropEntryWithChildTags(); + void testConditionalParsing_data(); + void testConditionalParsing(); + void testEntityParsing(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp b/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp new file mode 100644 index 000000000..2152d39de --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testdtorinformation.cpp @@ -0,0 +1,158 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testdtorinformation.h" +#include "abstractmetabuilder.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetalang.h> +#include <typesystem.h> + +void TestDtorInformation::testDtorIsPrivate() +{ + const char cppCode[] = R"(class Control { +public: + ~Control() {} +}; +class Subject { +private: + ~Subject() {} +}; +)"; + const char xmlCode[] = R"(<typesystem package="Foo"> + <value-type name="Control"/> + <value-type name="Subject"/> +</typesystem>)"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + auto klass = AbstractMetaClass::findClass(classes, "Control"); + QVERIFY(klass); + QVERIFY(!klass->hasPrivateDestructor()); + klass = AbstractMetaClass::findClass(classes, "Subject"); + QVERIFY(klass); + QVERIFY(klass->hasPrivateDestructor()); +} + +void TestDtorInformation::testDtorIsProtected() +{ + const char cppCode[] = R"(class Control { +public: + ~Control() {} +}; +class Subject { +protected: + ~Subject() {} +}; +)"; + const char xmlCode[] = R"(<typesystem package="Foo"> + <value-type name="Control"/> + <value-type name="Subject"/> +</typesystem>)"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + auto klass = AbstractMetaClass::findClass(classes, "Control"); + QVERIFY(klass); + QVERIFY(!klass->hasProtectedDestructor()); + klass = AbstractMetaClass::findClass(classes, "Subject"); + QVERIFY(klass); + QVERIFY(klass->hasProtectedDestructor()); +} + +void TestDtorInformation::testDtorIsVirtual() +{ + const char cppCode[] = R"(class Control { +public: + ~Control() {} +}; +class Subject { +protected: + virtual ~Subject() {} +}; +)"; + const char xmlCode[] = R"(<typesystem package="Foo"> + <value-type name="Control"/> + <value-type name="Subject"/> +</typesystem>)"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + auto klass = AbstractMetaClass::findClass(classes, "Control"); + QVERIFY(klass); + QVERIFY(!klass->hasVirtualDestructor()); + klass = AbstractMetaClass::findClass(classes, "Subject"); + QVERIFY(klass); + QVERIFY(klass->hasVirtualDestructor()); +} + +void TestDtorInformation::testDtorFromBaseIsVirtual() +{ + const char cppCode[] = R"CPP(class ControlBase { public: ~ControlBase() {} }; +class Control : public ControlBase {}; +class SubjectBase { public: virtual ~SubjectBase() {} }; +class Subject : public SubjectBase {}; +)CPP"; + const char xmlCode[] = R"XML(<typesystem package="Foo"><value-type name="ControlBase"/> +<value-type name="Control"/>" +<value-type name="SubjectBase"/>" +<value-type name="Subject"/> +</typesystem> +)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 4); + + auto klass = AbstractMetaClass::findClass(classes, "ControlBase"); + QVERIFY(klass); + QVERIFY(!klass->hasVirtualDestructor()); + klass = AbstractMetaClass::findClass(classes, "Control"); + QVERIFY(klass); + QVERIFY(!klass->hasVirtualDestructor()); + + klass = AbstractMetaClass::findClass(classes, "SubjectBase"); + QVERIFY(klass); + QVERIFY(klass->hasVirtualDestructor()); + klass = AbstractMetaClass::findClass(classes, "Subject"); + QVERIFY(klass); + QVERIFY(klass->hasVirtualDestructor()); +} + +void TestDtorInformation::testClassWithVirtualDtorIsPolymorphic() +{ + const char cppCode[] = R"(class Control { +public: + virtual ~Control() {} +}; +class Subject { +protected: + virtual ~Subject() {} +}; +)"; + const char xmlCode[] = R"(<typesystem package="Foo"> + <value-type name="Control"/> + <value-type name="Subject"/> +</typesystem>)"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + auto klass = AbstractMetaClass::findClass(classes, "Control"); + QVERIFY(klass); + QVERIFY(klass->isPolymorphic()); + klass = AbstractMetaClass::findClass(classes, "Subject"); + QVERIFY(klass); + QVERIFY(klass->isPolymorphic()); +} + +QTEST_APPLESS_MAIN(TestDtorInformation) + + diff --git a/sources/shiboken6/ApiExtractor/tests/testdtorinformation.h b/sources/shiboken6/ApiExtractor/tests/testdtorinformation.h new file mode 100644 index 000000000..0f8cb59b3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testdtorinformation.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTDTORINFORMATION_H +#define TESTDTORINFORMATION_H + +#include <QtCore/QObject> + +class AbstractMetaBuilder; + +class TestDtorInformation: public QObject +{ + Q_OBJECT +private slots: + void testDtorIsPrivate(); + void testDtorIsProtected(); + void testDtorIsVirtual(); + void testDtorFromBaseIsVirtual(); + void testClassWithVirtualDtorIsPolymorphic(); +}; + +#endif // TESTDTORINFORMATION_H diff --git a/sources/shiboken6/ApiExtractor/tests/testenum.cpp b/sources/shiboken6/ApiExtractor/tests/testenum.cpp new file mode 100644 index 000000000..c7c2b8b3b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testenum.cpp @@ -0,0 +1,577 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testenum.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetaenum.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetabuilder_p.h> +#include <enumtypeentry.h> +#include <flagstypeentry.h> +#include <parser/enumvalue.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestEnum::testEnumCppSignature() +{ + const char cppCode[] = "\ + enum GlobalEnum { A, B };\n\ + \n\ + struct A {\n\ + enum ClassEnum { CA, CB };\n\ + void method(ClassEnum);\n\ + };\n\ + void func(A::ClassEnum);\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <enum-type name='GlobalEnum'/>\n\ + <value-type name='A'>\n\ + <enum-type name='ClassEnum'/>\n\ + </value-type>\n\ + <function signature='func(A::ClassEnum)'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + + AbstractMetaEnumList globalEnums = builder->globalEnums(); + QCOMPARE(globalEnums.size(), 1); + QCOMPARE(globalEnums.constFirst().name(), u"GlobalEnum"); + + // enum as parameter of a function + const auto functions = builder->globalFunctions(); + QCOMPARE(functions.size(), 1); + QCOMPARE(functions.constFirst()->arguments().size(), 1); + QCOMPARE(functions.constFirst()->arguments().constFirst().type().cppSignature(), + u"A::ClassEnum"); + + // enum as parameter of a method + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QCOMPARE(classA->enums().size(), 1); + const auto funcs = classA->queryFunctionsByName(u"method"_s); + QVERIFY(!funcs.isEmpty()); + const auto method = funcs.constFirst(); + AbstractMetaArgument arg = method->arguments().constFirst(); + QCOMPARE(arg.type().name(), u"ClassEnum"); + QCOMPARE(arg.type().cppSignature(), u"A::ClassEnum"); + QCOMPARE(functions.constFirst()->arguments().size(), 1); + arg = functions.constFirst()->arguments().constFirst(); + QCOMPARE(arg.type().name(), u"ClassEnum"); + QCOMPARE(arg.type().cppSignature(), u"A::ClassEnum"); + + AbstractMetaEnumList classEnums = classA->enums(); + QVERIFY(!classEnums.isEmpty()); + QCOMPARE(classEnums.constFirst().name(), u"ClassEnum"); + auto e = AbstractMetaClass::findEnumValue(classes, u"CA"_s); + QVERIFY(e.has_value()); + e = AbstractMetaClass::findEnumValue(classes, u"ClassEnum::CA"_s); + QVERIFY(e.has_value()); +} + +void TestEnum::testEnumWithApiVersion() +{ + const char cppCode[] = "\ + struct A {\n\ + enum ClassEnum { EnumA, EnumB };\n\ + enum ClassEnum2 { EnumC, EnumD };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'>\n\ + <enum-type name='ClassEnum' since='0.1'/>\n\ + <enum-type name='ClassEnum2' since='0.2'/>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + true, u"0.1"_s)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + QCOMPARE(classes[0]->enums().size(), 1); +} + +void TestEnum::testAnonymousEnum() +{ + const char cppCode[] = "\ + enum { Global0, Global1 };\n\ + struct A {\n\ + enum { A0, A1 };\n\ + enum { isThis = true, isThat = false };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <!-- Uses the first value of the enum to identify it. -->\n\ + <enum-type identified-by-value='Global0'/>\n\ + <value-type name='A'>\n\ + <!-- Uses the second value of the enum to identify it. -->\n\ + <enum-type identified-by-value='A1'/>\n\ + <enum-type identified-by-value='isThis'/>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaEnumList globalEnums = builder->globalEnums(); + QCOMPARE(globalEnums.size(), 1); + QCOMPARE(globalEnums.constFirst().typeEntry()->qualifiedCppName(), + u"Global0"); + QVERIFY(globalEnums.constFirst().isAnonymous()); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + QCOMPARE(classes[0]->enums().size(), 2); + + auto anonEnumA1 = classes[0]->findEnum(u"A1"_s); + QVERIFY(anonEnumA1.has_value()); + QVERIFY(anonEnumA1->isAnonymous()); + QCOMPARE(anonEnumA1->typeEntry()->qualifiedCppName(), u"A::A1"); + + AbstractMetaEnumValue enumValueA0 = anonEnumA1->values().constFirst(); + QCOMPARE(enumValueA0.name(), u"A0"); + QCOMPARE(enumValueA0.value().value(), 0); + QCOMPARE(enumValueA0.stringValue(), QString()); + + AbstractMetaEnumValue enumValueA1 = anonEnumA1->values().constLast(); + QCOMPARE(enumValueA1.name(), u"A1"); + QCOMPARE(enumValueA1.value().value(), 1); + QCOMPARE(enumValueA1.stringValue(), QString()); + + auto anonEnumIsThis = classes[0]->findEnum(u"isThis"_s); + QVERIFY(anonEnumIsThis.has_value()); + QVERIFY(anonEnumIsThis->isAnonymous()); + QCOMPARE(anonEnumIsThis->typeEntry()->qualifiedCppName(), u"A::isThis"); + + AbstractMetaEnumValue enumValueIsThis = anonEnumIsThis->values().constFirst(); + QCOMPARE(enumValueIsThis.name(), u"isThis"); + QCOMPARE(enumValueIsThis.value().value(), static_cast<int>(true)); + QCOMPARE(enumValueIsThis.stringValue(), u"true"); + + AbstractMetaEnumValue enumValueIsThat = anonEnumIsThis->values().constLast(); + QCOMPARE(enumValueIsThat.name(), u"isThat"); + QCOMPARE(enumValueIsThat.value().value(), static_cast<int>(false)); + QCOMPARE(enumValueIsThat.stringValue(), u"false"); +} + +void TestEnum::testGlobalEnums() +{ + const char cppCode[] = "\ + enum EnumA { A0, A1 };\n\ + enum EnumB { B0 = 2, B1 = 0x4 };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <enum-type name='EnumA'/>\n\ + <enum-type name='EnumB'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaEnumList globalEnums = builder->globalEnums(); + QCOMPARE(globalEnums.size(), 2); + + AbstractMetaEnum enumA = globalEnums.constFirst(); + QCOMPARE(enumA.typeEntry()->qualifiedCppName(), u"EnumA"); + + AbstractMetaEnumValue enumValueA0 = enumA.values().constFirst(); + QCOMPARE(enumValueA0.name(), u"A0"); + QCOMPARE(enumValueA0.value().value(), 0); + QCOMPARE(enumValueA0.stringValue(), QString()); + + AbstractMetaEnumValue enumValueA1 = enumA.values().constLast(); + QCOMPARE(enumValueA1.name(), u"A1"); + QCOMPARE(enumValueA1.value().value(), 1); + QCOMPARE(enumValueA1.stringValue(), QString()); + + AbstractMetaEnum enumB = globalEnums.constLast(); + QCOMPARE(enumB.typeEntry()->qualifiedCppName(), u"EnumB"); + + AbstractMetaEnumValue enumValueB0 = enumB.values().constFirst(); + QCOMPARE(enumValueB0.name(), u"B0"); + QCOMPARE(enumValueB0.value().value(), 2); + QCOMPARE(enumValueB0.stringValue(), u"2"); + + AbstractMetaEnumValue enumValueB1 = enumB.values().constLast(); + QCOMPARE(enumValueB1.name(), u"B1"); + QCOMPARE(enumValueB1.value().value(), 4); + QCOMPARE(enumValueB1.stringValue(), u"0x4"); +} + +void TestEnum::testEnumValueFromNeighbourEnum() +{ + const char cppCode[] = "\ + namespace A {\n\ + enum EnumA { ValueA0, ValueA1 };\n\ + enum EnumB { ValueB0 = A::ValueA1, ValueB1 = ValueA0 };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <namespace-type name='A'>\n\ + <enum-type name='EnumA'/>\n\ + <enum-type name='EnumB'/>\n\ + </namespace-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + QCOMPARE(classes[0]->enums().size(), 2); + + auto enumA = classes[0]->findEnum(u"EnumA"_s); + QVERIFY(enumA.has_value()); + QCOMPARE(enumA->typeEntry()->qualifiedCppName(), u"A::EnumA"); + + AbstractMetaEnumValue enumValueA0 = enumA->values().constFirst(); + QCOMPARE(enumValueA0.name(), u"ValueA0"); + QCOMPARE(enumValueA0.value().value(), 0); + QCOMPARE(enumValueA0.stringValue(), QString()); + + AbstractMetaEnumValue enumValueA1 = enumA->values().constLast(); + QCOMPARE(enumValueA1.name(), u"ValueA1"); + QCOMPARE(enumValueA1.value().value(), 1); + QCOMPARE(enumValueA1.stringValue(), QString()); + + auto enumB = classes[0]->findEnum(u"EnumB"_s); + QVERIFY(enumB.has_value()); + QCOMPARE(enumB->typeEntry()->qualifiedCppName(), u"A::EnumB"); + + AbstractMetaEnumValue enumValueB0 = enumB->values().constFirst(); + QCOMPARE(enumValueB0.name(), u"ValueB0"); + QCOMPARE(enumValueB0.value().value(), 1); + QCOMPARE(enumValueB0.stringValue(), u"A::ValueA1"); + + AbstractMetaEnumValue enumValueB1 = enumB->values().constLast(); + QCOMPARE(enumValueB1.name(), u"ValueB1"); + QCOMPARE(enumValueB1.value().value(), 0); + QCOMPARE(enumValueB1.stringValue(), u"ValueA0"); +} + +void TestEnum::testEnumValueFromExpression() +{ + const char cppCode[] = "\ + struct A {\n\ + enum EnumA : unsigned {\n\ + ValueA0 = 3u,\n\ + ValueA1 = ~3u,\n\ + ValueA2 = 0xffffffff,\n\ + ValueA3 = 0xf0,\n\ + ValueA4 = 8 |ValueA3,\n\ + ValueA5 = ValueA3|32,\n\ + ValueA6 = ValueA3 >> 1,\n\ + ValueA7 = ValueA3 << 1\n\ + };\n\ + enum EnumB : int {\n\ + ValueB0 = ~3,\n\ + };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'>\n\ + <enum-type name='EnumA'/>\n\ + <enum-type name='EnumB'/>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaClassPtr classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + + auto enumA = classA->findEnum(u"EnumA"_s); + QVERIFY(enumA.has_value()); + QVERIFY(!enumA->isSigned()); + QCOMPARE(enumA->typeEntry()->qualifiedCppName(), u"A::EnumA"); + + AbstractMetaEnumValue valueA0 = enumA->values().at(0); + QCOMPARE(valueA0.name(), u"ValueA0"); + QCOMPARE(valueA0.stringValue(), u"3u"); + QCOMPARE(valueA0.value().unsignedValue(), 3u); + + AbstractMetaEnumValue valueA1 = enumA->values().at(1); + QCOMPARE(valueA1.name(), u"ValueA1"); + QCOMPARE(valueA1.stringValue(), u"~3u"); + QCOMPARE(valueA1.value().unsignedValue(), ~3u); + + AbstractMetaEnumValue valueA2 = enumA->values().at(2); + QCOMPARE(valueA2.name(), u"ValueA2"); + QCOMPARE(valueA2.stringValue(), u"0xffffffff"); + QCOMPARE(valueA2.value().unsignedValue(), 0xffffffffu); + + AbstractMetaEnumValue valueA3 = enumA->values().at(3); + QCOMPARE(valueA3.name(), u"ValueA3"); + QCOMPARE(valueA3.stringValue(), u"0xf0"); + QCOMPARE(valueA3.value().unsignedValue(), 0xf0u); + + AbstractMetaEnumValue valueA4 = enumA->values().at(4); + QCOMPARE(valueA4.name(), u"ValueA4"); + QCOMPARE(valueA4.stringValue(), u"8 |ValueA3"); + QCOMPARE(valueA4.value().unsignedValue(), 8|0xf0u); + + AbstractMetaEnumValue valueA5 = enumA->values().at(5); + QCOMPARE(valueA5.name(), u"ValueA5"); + QCOMPARE(valueA5.stringValue(), u"ValueA3|32"); + QCOMPARE(valueA5.value().unsignedValue(), 0xf0u|32); + + AbstractMetaEnumValue valueA6 = enumA->values().at(6); + QCOMPARE(valueA6.name(), u"ValueA6"); + QCOMPARE(valueA6.stringValue(), u"ValueA3 >> 1"); + QCOMPARE(valueA6.value().unsignedValue(), 0xf0u >> 1); + + AbstractMetaEnumValue valueA7 = enumA->values().at(7); + QCOMPARE(valueA7.name(), u"ValueA7"); + QCOMPARE(valueA7.stringValue(), u"ValueA3 << 1"); + QCOMPARE(valueA7.value().unsignedValue(), 0xf0u << 1); + + const auto enumB = classA->findEnum(u"EnumB"_s); + QVERIFY(enumB.has_value()); + QVERIFY(enumB->isSigned()); + QCOMPARE(enumB->typeEntry()->qualifiedCppName(), u"A::EnumB"); + QCOMPARE(enumB->values().size(), 1); + const AbstractMetaEnumValue valueB0 = enumB->values().at(0); + QCOMPARE(valueB0.name(), u"ValueB0"); + QCOMPARE(valueB0.stringValue(), u"~3"); + QCOMPARE(valueB0.value().value(), ~3); +} + +void TestEnum::testPrivateEnum() +{ + const char cppCode[] = "\ + class A {\n\ + private:\n\ + enum PrivateEnum { Priv0 = 0x0f, Priv1 = 0xf0 };\n\ + public:\n\ + enum PublicEnum { Pub0 = Priv0, Pub1 = A::Priv1 };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'>\n\ + <enum-type name='PublicEnum'/>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + QCOMPARE(classA->enums().size(), 2); + + auto privateEnum = classA->findEnum(u"PrivateEnum"_s); + QVERIFY(privateEnum.has_value()); + QVERIFY(privateEnum->isPrivate()); + QCOMPARE(privateEnum->typeEntry()->qualifiedCppName(), u"A::PrivateEnum"); + + auto publicEnum = classA->findEnum(u"PublicEnum"_s); + QVERIFY(publicEnum.has_value()); + QCOMPARE(publicEnum->typeEntry()->qualifiedCppName(), u"A::PublicEnum"); + + AbstractMetaEnumValue pub0 = publicEnum->values().constFirst(); + QCOMPARE(pub0.name(), u"Pub0"); + QCOMPARE(pub0.value().value(), 0x0f); + QCOMPARE(pub0.stringValue(), u"Priv0"); + + AbstractMetaEnumValue pub1 = publicEnum->values().constLast(); + QCOMPARE(pub1.name(), u"Pub1"); + QCOMPARE(pub1.value().value(), 0xf0); + QCOMPARE(pub1.stringValue(), u"A::Priv1"); +} + +void TestEnum::testTypedefEnum() +{ + const char cppCode[] = "\ + typedef enum EnumA {\n\ + A0,\n\ + A1,\n\ + } EnumA;\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <enum-type name='EnumA'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaEnumList globalEnums = builder->globalEnums(); + QCOMPARE(globalEnums.size(), 1); + + AbstractMetaEnum enumA = globalEnums.constFirst(); + QCOMPARE(enumA.typeEntry()->qualifiedCppName(), u"EnumA"); + + AbstractMetaEnumValue enumValueA0 = enumA.values().constFirst(); + QCOMPARE(enumValueA0.name(), u"A0"); + QCOMPARE(enumValueA0.value().value(), 0); + QCOMPARE(enumValueA0.stringValue(), u""); + + AbstractMetaEnumValue enumValueA1 = enumA.values().constLast(); + QCOMPARE(enumValueA1.name(), u"A1"); + QCOMPARE(enumValueA1.value().value(), 1); + QCOMPARE(enumValueA1.stringValue(), QString()); +} + +// Helper classes and functions for testing enum default value fixing. +// Put the AbstractMetaBuilder into test fixture struct to avoid having +// to re-parse for each data row. + +struct EnumDefaultValuesFixture +{ + std::shared_ptr<AbstractMetaBuilder> builder; + + AbstractMetaType globalEnum; + AbstractMetaType testEnum; + AbstractMetaType testOptions; +}; + +Q_DECLARE_METATYPE(EnumDefaultValuesFixture) +Q_DECLARE_METATYPE(AbstractMetaType) + +static int populateDefaultValuesFixture(EnumDefaultValuesFixture *fixture) +{ + static const char cppCode[] =R"( +enum GlobalEnum { GE1, GE2 }; +namespace Test1 +{ +namespace Test2 +{ + enum Enum1 { E1, E2 }; + enum Option { O1, O2 }; +} // namespace Test2 +} // namespace Test1 +)"; + static const char xmlCode[] = R"( +<typesystem package="Foo"> + <enum-type name='GlobalEnum'/> + <namespace-type name='Test1'> + <namespace-type name='Test2'> + <enum-type name='Enum1'/> + <enum-type name='Option' flags='Options'/> + </namespace-type> + </namespace-type> +</typesystem> +)"; + + fixture->builder.reset(TestUtil::parse(cppCode, xmlCode, false)); + if (!fixture->builder) + return -1; + + const auto globalEnums = fixture->builder->globalEnums(); + if (globalEnums.size() != 1) + return -2; + + fixture->globalEnum = AbstractMetaType(globalEnums.constFirst().typeEntry()); + fixture->globalEnum.decideUsagePattern(); + + AbstractMetaClassCPtr testNamespace; + for (const auto &c : fixture->builder->classes()) { + if (c->name() == u"Test2") { + testNamespace = c; + break; + } + } + if (!testNamespace) + return -3; + + const auto namespaceEnums = testNamespace->enums(); + if (namespaceEnums.size() != 2) + return -4; + QList<EnumTypeEntryCPtr > enumTypeEntries{ + std::static_pointer_cast<const EnumTypeEntry>(namespaceEnums.at(0).typeEntry()), + std::static_pointer_cast<const EnumTypeEntry>(namespaceEnums.at(1).typeEntry())}; + if (enumTypeEntries.constFirst()->flags()) + std::swap(enumTypeEntries[0], enumTypeEntries[1]); + fixture->testEnum = AbstractMetaType(enumTypeEntries.at(0)); + fixture->testEnum.decideUsagePattern(); + fixture->testOptions = AbstractMetaType(enumTypeEntries.at(1)->flags()); + fixture->testOptions.decideUsagePattern(); + return 0; +} + +void TestEnum::testEnumDefaultValues_data() +{ + EnumDefaultValuesFixture fixture; + const int setupOk = populateDefaultValuesFixture(&fixture); + + QTest::addColumn<EnumDefaultValuesFixture>("fixture"); + QTest::addColumn<int>("setupOk"); // To verify setup + QTest::addColumn<AbstractMetaType>("metaType"); // Type and parameters for fixup + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("expected"); + + // Global should just remain unmodified + QTest::newRow("global") << fixture << setupOk + << fixture.globalEnum << "GE1" << "GE1"; + QTest::newRow("global-int") << fixture << setupOk + << fixture.globalEnum << "42" << "42"; + QTest::newRow("global-hex-int") << fixture << setupOk + << fixture.globalEnum << "0x10" << "0x10"; + QTest::newRow("global-int-cast") << fixture << setupOk + << fixture.globalEnum << "GlobalEnum(-1)" << "GlobalEnum(-1)"; + + // Namespaced enum as number should remain unmodified + QTest::newRow("namespace-enum-int") << fixture << setupOk + << fixture.testEnum << "42" << "42"; + QTest::newRow("namespace-enum-hex-int") << fixture << setupOk + << fixture.testEnum << "0x10" << "0x10"; + // Partial qualification of namespaced enum + QTest::newRow("namespace-enum-qualified") << fixture << setupOk + << fixture.testEnum << "Enum1::E1" << "Test1::Test2::Enum1::E1"; + // Unqualified namespaced enums + QTest::newRow("namespace-enum-unqualified") << fixture << setupOk + << fixture.testEnum << "E1" << "Test1::Test2::Enum1::E1"; + // Namespaced enums cast from int should be qualified by scope + QTest::newRow("namespace-enum-int-cast") << fixture << setupOk + << fixture.testEnum << "Enum1(-1)" << "Test1::Test2::Enum1(-1)"; + + // Namespaced option as number should remain unmodified + QTest::newRow("namespace-option-int") << fixture << setupOk + << fixture.testOptions << "0x10" << "0x10"; + QTest::newRow("namespace-option-expression") << fixture << setupOk + << fixture.testOptions << "0x10 | 0x20" << "0x10 | 0x20"; + QTest::newRow("namespace-option-expression1") << fixture << setupOk + << fixture.testOptions << "0x10 | Test1::Test2::Option::O1" + << "0x10 | Test1::Test2::Option::O1"; + QTest::newRow("namespace-option-expression2") << fixture << setupOk + << fixture.testOptions << "0x10 | O1" << "0x10 | Test1::Test2::Option::O1"; + // Complicated expressions - should remain unmodified + QTest::newRow("namespace-option-expression-paren") << fixture << setupOk + << fixture.testOptions << "0x10 | (0x20 | 0x40 | O1)" + << "0x10 | (0x20 | 0x40 | O1)"; + + // Option: Cast Enum from int should be qualified + QTest::newRow("namespace-option-int-cast") << fixture << setupOk + << fixture.testOptions << "Option(0x10)" << "Test1::Test2::Option(0x10)"; + // Option: Cast Flags from int should be qualified + QTest::newRow("namespace-options-int-cast") << fixture << setupOk + << fixture.testOptions << "Options(0x10 | 0x20)" << "Test1::Test2::Options(0x10 | 0x20)"; + QTest::newRow("namespace-option-cast-expression1") << fixture << setupOk + << fixture.testOptions << "Test1::Test2::Options(0x10 | Test1::Test2::Option::O1)" + << "Test1::Test2::Options(0x10 | Test1::Test2::Option::O1)"; + QTest::newRow("namespace-option-cast-expression2") << fixture << setupOk + << fixture.testOptions << "Options(0x10 | O1)" + << "Test1::Test2::Options(0x10 | Test1::Test2::Option::O1)"; +} + +void TestEnum::testEnumDefaultValues() +{ + QFETCH(EnumDefaultValuesFixture, fixture); + QFETCH(int, setupOk); + QFETCH(AbstractMetaType, metaType); + QFETCH(QString, input); + QFETCH(QString, expected); + QCOMPARE(setupOk, 0); + const QString actual = fixture.builder->fixEnumDefault(metaType, input); + QCOMPARE(actual, expected); +} + +QTEST_APPLESS_MAIN(TestEnum) diff --git a/sources/shiboken6/ApiExtractor/tests/testenum.h b/sources/shiboken6/ApiExtractor/tests/testenum.h new file mode 100644 index 000000000..452755490 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testenum.h @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTENUM_H +#define TESTENUM_H + +#include <QtCore/QObject> + +class TestEnum : public QObject +{ + Q_OBJECT +private slots: + void testEnumCppSignature(); + void testEnumWithApiVersion(); + void testAnonymousEnum(); + void testGlobalEnums(); + void testEnumValueFromNeighbourEnum(); + void testEnumValueFromExpression(); + void testPrivateEnum(); + void testTypedefEnum(); + void testEnumDefaultValues_data(); + void testEnumDefaultValues(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp b/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp new file mode 100644 index 000000000..fcc409a42 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testextrainclude.cpp @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testextrainclude.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetalang.h> +#include <complextypeentry.h> +#include <typesystemtypeentry.h> + +void TestExtraInclude::testClassExtraInclude() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'>\n\ + <extra-includes>\n\ + <include file-name='header.h' location='global'/>\n\ + </extra-includes>\n\ + </value-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + + QList<Include> includes = classA->typeEntry()->extraIncludes(); + QCOMPARE(includes.size(), 1); + QCOMPARE(includes.constFirst().name(), u"header.h"); +} + +void TestExtraInclude::testGlobalExtraIncludes() +{ + const char cppCode[] = "struct A {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <extra-includes>\n\ + <include file-name='header1.h' location='global'/>\n\ + <include file-name='header2.h' location='global'/>\n\ + </extra-includes>\n\ + <value-type name='A'/>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QVERIFY(AbstractMetaClass::findClass(classes, "A")); + + auto *td = TypeDatabase::instance(); + TypeSystemTypeEntryCPtr module = td->defaultTypeSystemType(); + QVERIFY(module); + QCOMPARE(module->name(), u"Foo"); + + QList<Include> includes = module->extraIncludes(); + QCOMPARE(includes.size(), 2); + QCOMPARE(includes.constFirst().name(), u"header1.h"); + QCOMPARE(includes.constLast().name(), u"header2.h"); +} + +QTEST_APPLESS_MAIN(TestExtraInclude) diff --git a/sources/shiboken6/ApiExtractor/tests/testextrainclude.h b/sources/shiboken6/ApiExtractor/tests/testextrainclude.h new file mode 100644 index 000000000..6bcb57993 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testextrainclude.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTEXTRAINCLUDE_H +#define TESTEXTRAINCLUDE_H + +#include <QtCore/QObject> + +class TestExtraInclude : public QObject +{ + Q_OBJECT + private slots: + void testClassExtraInclude(); + void testGlobalExtraIncludes(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testfunctiontag.cpp b/sources/shiboken6/ApiExtractor/tests/testfunctiontag.cpp new file mode 100644 index 000000000..18eaf5774 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testfunctiontag.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testfunctiontag.h" +#include "testutil.h" +#include <abstractmetafunction.h> +#include <modifications.h> +#include <typesystem.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestFunctionTag::testFunctionTagForSpecificSignature() +{ + const char cppCode[] = "void globalFunction(int); void globalFunction(float); void dummy();\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <primitive-type name='int'/>\n\ + <primitive-type name='float'/>\n\ + <function signature='globalFunction(int)'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + TypeEntryCPtr func = TypeDatabase::instance()->findType(u"globalFunction"_s); + QVERIFY(func); + QCOMPARE(builder->globalFunctions().size(), 1); +} + +void TestFunctionTag::testFunctionTagForAllSignatures() +{ + const char cppCode[] = "void globalFunction(int); void globalFunction(float); void dummy();\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <primitive-type name='int'/>\n\ + <primitive-type name='float'/>\n\ + <function signature='globalFunction(int)'/>\n\ + <function signature='globalFunction(float)'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + TypeEntryCPtr func = TypeDatabase::instance()->findType(u"globalFunction"_s); + QVERIFY(func); + QCOMPARE(builder->globalFunctions().size(), 2); +} + +void TestFunctionTag::testRenameGlobalFunction() +{ + const char cppCode[] = "void global_function_with_ugly_name();\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <function signature='global_function_with_ugly_name()' rename='smooth'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + TypeEntryCPtr func = TypeDatabase::instance()->findType(u"global_function_with_ugly_name"_s); + QVERIFY(func); + + QCOMPARE(builder->globalFunctions().size(), 1); + const auto metaFunc = builder->globalFunctions().constFirst(); + + QVERIFY(metaFunc); + QCOMPARE(metaFunc->modifications().size(), 1); + QVERIFY(metaFunc->modifications().constFirst().isRenameModifier()); + QCOMPARE(metaFunc->modifications().constFirst().renamedToName(), + u"smooth"); + + QCOMPARE(metaFunc->name(), u"smooth"); + QCOMPARE(metaFunc->originalName(), u"global_function_with_ugly_name"); + QCOMPARE(metaFunc->minimalSignature(), u"global_function_with_ugly_name()"); +} + +QTEST_APPLESS_MAIN(TestFunctionTag) + diff --git a/sources/shiboken6/ApiExtractor/tests/testfunctiontag.h b/sources/shiboken6/ApiExtractor/tests/testfunctiontag.h new file mode 100644 index 000000000..7c60cb4e0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testfunctiontag.h @@ -0,0 +1,18 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTFUNCTIONTAG_H +#define TESTFUNCTIONTAG_H + +#include <QtCore/QObject> + +class TestFunctionTag : public QObject +{ + Q_OBJECT +private slots: + void testFunctionTagForSpecificSignature(); + void testFunctionTagForAllSignatures(); + void testRenameGlobalFunction(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp b/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp new file mode 100644 index 000000000..899d00ad4 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.cpp @@ -0,0 +1,142 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testimplicitconversions.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <complextypeentry.h> +#include <QtTest/QTest> + +void TestImplicitConversions::testWithPrivateCtors() +{ + const char cppCode[] = "\ + class B;\n\ + class C;\n\ + class A {\n\ + A(const B&);\n\ + public:\n\ + A(const C&);\n\ + };\n\ + class B {};\n\ + class C {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + <value-type name='C'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 3); + + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classC = AbstractMetaClass::findClass(classes, "C"); + const auto implicitConvs = classA->implicitConversions(); + QCOMPARE(implicitConvs.size(), 1); + QCOMPARE(implicitConvs.constFirst()->arguments().constFirst().type().typeEntry(), + classC->typeEntry()); +} + +void TestImplicitConversions::testWithModifiedVisibility() +{ + const char cppCode[] = "\ + class B;\n\ + class A {\n\ + public:\n\ + A(const B&);\n\ + };\n\ + class B {};\n"; + const char xmlCode[] = R"( +<typesystem package='Foo'> + <value-type name='A'> + <modify-function signature='A(const B&)' access='private'/> + </value-type> + <value-type name='B'/> +</typesystem> +)"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto implicitConvs = classA->implicitConversions(); + QCOMPARE(implicitConvs.size(), 1); + QCOMPARE(implicitConvs.constFirst()->arguments().constFirst().type().typeEntry(), + classB->typeEntry()); +} + + +void TestImplicitConversions::testWithAddedCtor() +{ + const char cppCode[] = "\ + class B;\n\ + class A {\n\ + public:\n\ + A(const B&);\n\ + };\n\ + class B {};\n\ + class C {};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <custom-type name='TARGETLANGTYPE'/>\n\ + <value-type name='A'>\n\ + <add-function signature='A(const C&)'/>\n\ + </value-type>\n\ + <value-type name='B'>\n\ + <add-function signature='B(TARGETLANGTYPE*)'/>\n\ + </value-type>\n\ + <value-type name='C'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 3); + + const auto classA = AbstractMetaClass::findClass(classes, "A"); + auto implicitConvs = classA->implicitConversions(); + QCOMPARE(implicitConvs.size(), 2); + + // Added constructors with custom types should never result in implicit converters. + const auto classB = AbstractMetaClass::findClass(classes, "B"); + implicitConvs = classB->implicitConversions(); + QCOMPARE(implicitConvs.size(), 0); +} + +void TestImplicitConversions::testWithExternalConversionOperator() +{ + const char cppCode[] = "\ + class A {};\n\ + struct B {\n\ + operator A() const;\n\ + };\n"; + const char xmlCode[] = "\n\ + <typesystem package='Foo'>\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto implicitConvs = classA->implicitConversions(); + QCOMPARE(implicitConvs.size(), 1); + const auto &externalConvOps = classA->externalConversionOperators(); + QCOMPARE(externalConvOps.size(), 1); + + AbstractMetaFunctionCPtr convOp; + for (const auto &func : classB->functions()) { + if (func->isConversionOperator()) + convOp = func; + } + QVERIFY(convOp); + QCOMPARE(implicitConvs.constFirst(), convOp); +} + +QTEST_APPLESS_MAIN(TestImplicitConversions) diff --git a/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.h b/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.h new file mode 100644 index 000000000..e0678c5f5 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testimplicitconversions.h @@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTIMPLICITCONVERSIONS_H +#define TESTIMPLICITCONVERSIONS_H + +#include <QtCore/QObject> + +class AbstractMetaBuilder; + +class TestImplicitConversions : public QObject +{ + Q_OBJECT +private slots: + void testWithPrivateCtors(); + void testWithModifiedVisibility(); + void testWithAddedCtor(); + void testWithExternalConversionOperator(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp b/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp new file mode 100644 index 000000000..23cf0f9ea --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testinserttemplate.cpp @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testinserttemplate.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetalang.h> +#include <codesnip.h> +#include <modifications.h> +#include <complextypeentry.h> +#include <typesystemtypeentry.h> + +void TestInsertTemplate::testInsertTemplateOnClassInjectCode() +{ + const char cppCode[] = "struct A{};\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <template name='code_template'>\n\ + code template content\n\ + </template>\n\ + <value-type name='A'>\n\ + <inject-code class='native'>\n\ + <insert-template name='code_template'/>\n\ + </inject-code>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->typeEntry()->codeSnips().size(), 1); + QString code = classA->typeEntry()->codeSnips().constFirst().code(); + QVERIFY(code.contains(u"code template content")); +} + +void TestInsertTemplate::testInsertTemplateOnModuleInjectCode() +{ + const char cppCode[] = ""; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <template name='code_template'>\n\ + code template content\n\ + </template>\n\ + <inject-code class='native'>\n\ + <insert-template name='code_template'/>\n\ + </inject-code>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QVERIFY(classes.isEmpty()); + + TypeSystemTypeEntryCPtr module = TypeDatabase::instance()->defaultTypeSystemType(); + QVERIFY(module); + QCOMPARE(module->name(), u"Foo"); + QCOMPARE(module->codeSnips().size(), 1); + QString code = module->codeSnips().constFirst().code().trimmed(); + QVERIFY(code.contains(u"code template content")); +} + +QTEST_APPLESS_MAIN(TestInsertTemplate) diff --git a/sources/shiboken6/ApiExtractor/tests/testinserttemplate.h b/sources/shiboken6/ApiExtractor/tests/testinserttemplate.h new file mode 100644 index 000000000..f4f67abc0 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testinserttemplate.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTINSERTTEMPLATE_H +#define TESTINSERTTEMPLATE_H + +#include <QtCore/QObject> + +class TestInsertTemplate : public QObject +{ + Q_OBJECT + private slots: + void testInsertTemplateOnClassInjectCode(); + void testInsertTemplateOnModuleInjectCode(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp new file mode 100644 index 000000000..9cf2e0cc7 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.cpp @@ -0,0 +1,117 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testmodifydocumentation.h" +#include "testutil.h" +#include <abstractmetalang.h> +#include <abstractmetafunction.h> +#include <documentation.h> +#include <modifications.h> +#include <complextypeentry.h> +#include <qtdocparser.h> + +#include <qtcompat.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QTemporaryDir> +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestModifyDocumentation::testModifyDocumentation() +{ + const char cppCode[] = "struct B { void b(); }; class A {};\n"; + const char xmlCode[] = +R"(<typesystem package="Foo"> + <value-type name='B'> + <modify-function signature='b()' remove='all'/> + </value-type> + <value-type name='A'> + <modify-documentation xpath='description/brief'><brief>Modified Brief</brief></modify-documentation> + <modify-documentation xpath='description/para[3]'><para>Some changed contents here</para></modify-documentation> + </value-type> +</typesystem> +)"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + DocModificationList docMods = classA->typeEntry()->docModifications(); + QCOMPARE(docMods.size(), 2); + QCOMPARE(docMods[0].code().trimmed(), u"<brief>Modified Brief</brief>"); + QCOMPARE(docMods[0].signature(), QString()); + QCOMPARE(docMods[1].code().trimmed(), u"<para>Some changed contents here</para>"); + QCOMPARE(docMods[1].signature(), QString()); + + // Create a temporary directory for the documentation file since libxml2 + // cannot handle Qt resources. + QTemporaryDir tempDir(QDir::tempPath() + u"/shiboken_testmodifydocXXXXXX"_s); + QVERIFY2(tempDir.isValid(), qPrintable(tempDir.errorString())); + constexpr auto docFileName = "a.xml"_L1; + QVERIFY(QFile::copy(u":/"_s + docFileName, tempDir.filePath(docFileName))); + + QtDocParser docParser; + docParser.setDocumentationDataDirectory(tempDir.path()); + docParser.fillDocumentation(classA); + + const Documentation &doc = classA->documentation(); + const QString actualDocSimplified = doc.detailed().simplified(); + const QString actualBriefSimplified = doc.brief().simplified(); + QVERIFY(!actualDocSimplified.isEmpty()); + +const char expectedDoc[] = +R"(<?xml version="1.0"?> +<description>oi +<para>Paragraph number 1</para> +<para>Paragraph number 2</para> +<para>Some changed contents here</para> +</description> +)"; + const QString expectedDocSimplified = QString::fromLatin1(expectedDoc).simplified(); + // Check whether the first modification worked. + QVERIFY(actualBriefSimplified.contains(u"Modified Brief")); + +#ifndef HAVE_LIBXSLT + // QtXmlPatterns is unable to handle para[3] in style sheets, + // this only works in its XPath search. + QEXPECT_FAIL("", "QtXmlPatterns cannot handle para[3] (QTBUG-66925)", Abort); +#endif + QCOMPARE(actualDocSimplified, expectedDocSimplified); +} + +void TestModifyDocumentation::testInjectAddedFunctionDocumentation() +{ + const char cppCode[] ="class A {};\n"; + const char xmlCode[] = R"XML( +<typesystem package="Foo"> + <value-type name='A'> + <add-function signature="foo(int@parameter_name@)"> + <inject-documentation format="target" mode="append"> + Injected documentation of added function foo. + </inject-documentation> + </add-function> + </value-type> +</typesystem> +)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + const auto f = classA->findFunction("foo"); + QVERIFY(f); + QVERIFY(f->isUserAdded()); + auto docMods = f->addedFunctionDocModifications(); + QCOMPARE(docMods.size(), 1); + const QString code = docMods.constFirst().code(); + QVERIFY(code.contains(u"Injected documentation of added function foo.")); +} + +// We expand QTEST_MAIN macro but using QCoreApplication instead of QApplication +// because this test needs an event loop but can't use QApplication to avoid a crash +// on our ARMEL/FRAMANTLE buildbot +int main(int argc, char** argv) +{ + QCoreApplication app(argc, argv); + TestModifyDocumentation tc; + return QTest::qExec(&tc, argc, argv); +} diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.h b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.h new file mode 100644 index 000000000..c1cc8f480 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTMODIFYDOCUMENTATION_H +#define TESTMODIFYDOCUMENTATION_H + +#include <QtCore/QObject> + +class TestModifyDocumentation : public QObject +{ +Q_OBJECT +private slots: + void testModifyDocumentation(); + void testInjectAddedFunctionDocumentation(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.qrc b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.qrc new file mode 100644 index 000000000..76b1bfc61 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmodifydocumentation.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource> + <file>a.xml</file> + </qresource> +</RCC> diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp b/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp new file mode 100644 index 000000000..a7d40f70a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.cpp @@ -0,0 +1,480 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testmodifyfunction.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetabuilder_p.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <modifications.h> +#include <typesystem.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestModifyFunction::testRenameArgument_data() +{ + QTest::addColumn<QByteArray>("pattern"); + QTest::newRow("fixed_string") << QByteArrayLiteral("method(int)"); + QTest::newRow("regular_expression") << QByteArrayLiteral("^method.*"); +} + +void TestModifyFunction::testRenameArgument() +{ + QFETCH(QByteArray, pattern); + + const char cppCode[] = "\ + struct A {\n\ + void method(int=0);\n\ + };\n"; + const char xmlCode1[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <object-type name='A'>\n\ + <modify-function signature='"; + const char xmlCode2[] = R"('> + <modify-argument index='1' rename='otherArg'/> + </modify-function> + </object-type> + </typesystem> +)"; + + const QByteArray xmlCode = QByteArray(xmlCode1) + pattern + QByteArray(xmlCode2); + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode.constData(), false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + const auto func = classA->findFunction("method"); + QVERIFY(func); + + QCOMPARE(func->argumentName(1), u"otherArg"); +} + +void TestModifyFunction::testOwnershipTransfer() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + virtual A* method();\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <object-type name='A' />\n\ + <object-type name='B'>\n\ + <modify-function signature='method()'>\n\ + <modify-argument index='return'>\n\ + <define-ownership owner='c++'/>\n\ + </modify-argument>\n\ + </modify-function>\n\ + </object-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto func = classB->findFunction("method"); + QVERIFY(func); + + QCOMPARE(func->argumentTargetOwnership(func->ownerClass(), 0), + TypeSystem::CppOwnership); +} + + +void TestModifyFunction::invalidateAfterUse() +{ + const char cppCode[] = "\ + struct A {\n\ + virtual void call(int *a);\n\ + };\n\ + struct B : A {\n\ + };\n\ + struct C : B {\n\ + virtual void call2(int *a);\n\ + };\n\ + struct D : C {\n\ + virtual void call2(int *a);\n\ + };\n\ + struct E : D {\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='int'/>\n\ + <object-type name='A'>\n\ + <modify-function signature='call(int*)'>\n\ + <modify-argument index='1' invalidate-after-use='true'/>\n\ + </modify-function>\n\ + </object-type>\n\ + <object-type name='B' />\n\ + <object-type name='C'>\n\ + <modify-function signature='call2(int*)'>\n\ + <modify-argument index='1' invalidate-after-use='true'/>\n\ + </modify-function>\n\ + </object-type>\n\ + <object-type name='D'>\n\ + <modify-function signature='call2(int*)'>\n\ + <modify-argument index='1' invalidate-after-use='true'/>\n\ + </modify-function>\n\ + </object-type>\n\ + <object-type name='E' />\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + false, u"0.1"_s)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + auto func = classB->findFunction("call"); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); + + const auto classC = AbstractMetaClass::findClass(classes, "C"); + QVERIFY(classC); + func = classC->findFunction("call"); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); + + func = classC->findFunction("call2"); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); + + AbstractMetaClassCPtr classD = AbstractMetaClass::findClass(classes, "D"); + QVERIFY(classD); + func = classD->findFunction("call"); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); + + func = classD->findFunction("call2"); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); + + const auto classE = AbstractMetaClass::findClass(classes, "E"); + QVERIFY(classE); + func = classE->findFunction("call"); + QVERIFY(func); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); + + func = classE->findFunction("call2"); + QVERIFY(func); + QCOMPARE(func->modifications().size(), 1); + QCOMPARE(func->modifications().at(0).argument_mods().size(), 1); + QVERIFY(func->modifications().at(0).argument_mods().at(0).resetAfterUse()); +} + +void TestModifyFunction::testWithApiVersion() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + virtual A* method();\n\ + virtual B* methodB();\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <object-type name='A' />\n\ + <object-type name='B'>\n\ + <modify-function signature='method()' since='0.1'>\n\ + <modify-argument index='return'>\n\ + <define-ownership owner='c++'/>\n\ + </modify-argument>\n\ + </modify-function>\n\ + <modify-function signature='methodB()' since='0.2'>\n\ + <modify-argument index='return'>\n\ + <define-ownership owner='c++'/>\n\ + </modify-argument>\n\ + </modify-function>\n\ + </object-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + false, u"0.1"_s)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + auto func = classB->findFunction("method"); + + auto returnOwnership = func->argumentTargetOwnership(func->ownerClass(), 0); + QCOMPARE(returnOwnership, TypeSystem::CppOwnership); + + func = classB->findFunction("methodB"); + returnOwnership = func->argumentTargetOwnership(func->ownerClass(), 0); + QVERIFY(returnOwnership != TypeSystem::CppOwnership); +} + +// Modifications on class/typesystem level are tested below +// in testScopedModifications(). +void TestModifyFunction::testAllowThread() +{ + const char cppCode[] =R"CPP(\ +struct A { + void f1(); + void f2(); + void f3(); + int getter1() const; + int getter2() const; +}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='A'> + <modify-function signature='f2()' allow-thread='auto'/> + <modify-function signature='f3()' allow-thread='no'/> + <modify-function signature='getter2()const' allow-thread='yes'/> + </object-type> +</typesystem> +)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + false, u"0.1"_s)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + + // Nothing specified, true + const auto f1 = classA->findFunction("f1"); + QVERIFY(f1); + QVERIFY(!f1->allowThread()); + + // 'auto' specified, should be false for nontrivial function + const auto f2 = classA->findFunction("f2"); + QVERIFY(f2); + QVERIFY(f2->allowThread()); + + // 'no' specified, should be false + const auto f3 = classA->findFunction("f3"); + QVERIFY(f3); + QVERIFY(!f3->allowThread()); + + // Nothing specified, should be false for simple getter + const auto getter1 = classA->findFunction("getter1"); + QVERIFY(getter1); + QVERIFY(!getter1->allowThread()); + + // Forced to true simple getter + const auto getter2 = classA->findFunction("getter2"); + QVERIFY(getter2); + QVERIFY(getter2->allowThread()); // Forced to true simple getter +} + +void TestModifyFunction::testGlobalFunctionModification() +{ + const char cppCode[] = "\ + struct A {};\n\ + void function(A* a = 0);\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='A'/>\n\ + <function signature='function(A*)'>\n\ + <modify-function signature='function(A*)'>\n\ + <modify-argument index='1'>\n\ + <replace-type modified-type='A'/>\n\ + <replace-default-expression with='A()'/>\n\ + </modify-argument>\n\ + </modify-function>\n\ + </function>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + QCOMPARE(builder->globalFunctions().size(), 1); + + auto *td = TypeDatabase::instance(); + FunctionModificationList mods = td->globalFunctionModifications({u"function(A*)"_s}); + QCOMPARE(mods.size(), 1); + const QList<ArgumentModification> &argMods = mods.constFirst().argument_mods(); + QCOMPARE(argMods.size(), 1); + ArgumentModification argMod = argMods.constFirst(); + QCOMPARE(argMod.replacedDefaultExpression(), u"A()"); + + QVERIFY(!builder->globalFunctions().isEmpty()); + const auto func = builder->globalFunctions().constFirst(); + QCOMPARE(func->arguments().size(), 1); + const AbstractMetaArgument &arg = func->arguments().constFirst(); + QCOMPARE(arg.type().cppSignature(), u"A *"); + QCOMPARE(arg.originalDefaultValueExpression(), u"0"); + QCOMPARE(arg.defaultValueExpression(), u"A()"); +} + +// Tests modifications of exception handling and allow-thread +// on various levels. +void TestModifyFunction::testScopedModifications_data() +{ + QTest::addColumn<QByteArray>("cppCode"); + QTest::addColumn<QByteArray>("xmlCode"); + QTest::addColumn<bool>("expectedGenerateUnspecified"); + QTest::addColumn<bool>("expectedGenerateNonThrowing"); + QTest::addColumn<bool>("expectedGenerateThrowing"); + QTest::addColumn<bool>("expectedAllowThread"); + + const QByteArray cppCode = R"CPP( +struct Base { +}; + +struct A : public Base { + void unspecified(); + void nonThrowing() noexcept; +# if __cplusplus >= 201703L // C++ 17 + void throwing() noexcept(false); +#else + void throwing() throw(int); +#endif +}; +)CPP"; + + // Default: Off + QTest::newRow("none") + << cppCode + << QByteArray(R"XML( +<typesystem package= 'Foo'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='A'/> +</typesystem>)XML") + << false << false << false // exception + << false; // allowthread + + // Modify one function + QTest::newRow("modify-function1") + << cppCode + << QByteArray(R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='A'> + <modify-function signature='throwing()' exception-handling='auto-on'/> + </object-type> +</typesystem>)XML") + << false << false << true // exception + << false; // allowthread + + // Flip defaults by modifying functions + QTest::newRow("modify-function2") + << cppCode + << QByteArray(R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='A'> + <modify-function signature='unspecified()' exception-handling='auto-on'/> + <modify-function signature='throwing()' exception-handling='no'/> + </object-type> +</typesystem>)XML") + << true << false << false // exception + << false; // allowthread + + // Activate on type system level + QTest::newRow("typesystem-on") + << cppCode + << QByteArray(R"XML( +<typesystem package='Foo' exception-handling='auto-on' allow-thread='no'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='A'/> +</typesystem>)XML") + << true << false << true // exception + << false; // allowthread + + // Activate on class level + QTest::newRow("class-on") + << cppCode + << QByteArray(R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='A' exception-handling='auto-on' allow-thread='no'/> +</typesystem>)XML") + << true << false << true // exception + << false; // allowthread + + // Activate on base class level + QTest::newRow("baseclass-on") + << cppCode + << QByteArray(R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='Base' exception-handling='auto-on' allow-thread='no'/> + <object-type name='A'/> +</typesystem>)XML") + << true << false << true // exception + << false; // allowthread + + // Override value on class level + QTest::newRow("override-class-on") + << cppCode + << QByteArray(R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <object-type name='Base'/> + <object-type name='A' exception-handling='auto-on'> + <modify-function signature='throwing()' exception-handling='no'/> + </object-type> +</typesystem>)XML") + << true << false << false // exception + << false; // allowthread +} + +void TestModifyFunction::testScopedModifications() +{ + QFETCH(QByteArray, cppCode); + QFETCH(QByteArray, xmlCode); + QFETCH(bool, expectedGenerateUnspecified); + QFETCH(bool, expectedGenerateNonThrowing); + QFETCH(bool, expectedGenerateThrowing); + QFETCH(bool, expectedAllowThread); + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode.constData(), xmlCode.constData(), false)); + QVERIFY(builder); + + const auto classA = AbstractMetaClass::findClass(builder->classes(), "A"); + QVERIFY(classA); + + auto f = classA->findFunction("unspecified"); + QVERIFY(f); + QCOMPARE(f->exceptionSpecification(), ExceptionSpecification::Unknown); + QCOMPARE(f->generateExceptionHandling(), expectedGenerateUnspecified); + QCOMPARE(f->allowThread(), expectedAllowThread); + + f = classA->findFunction("nonThrowing"); + QVERIFY(f); + QCOMPARE(f->exceptionSpecification(), ExceptionSpecification::NoExcept); + QCOMPARE(f->generateExceptionHandling(), expectedGenerateNonThrowing); + + f = classA->findFunction("throwing"); + QVERIFY(f); + QCOMPARE(f->exceptionSpecification(), ExceptionSpecification::Throws); + QCOMPARE(f->generateExceptionHandling(), expectedGenerateThrowing); +} + +void TestModifyFunction::testSnakeCaseRenaming_data() +{ + QTest::addColumn<QLatin1StringView>("name"); + QTest::addColumn<QLatin1StringView>("expected"); + QTest::newRow("s1") + << "snakeCaseFunc"_L1 << "snake_case_func"_L1; + QTest::newRow("s2") + << "SnakeCaseFunc"_L1 << "snake_case_func"_L1; + QTest::newRow("consecutive-uppercase") + << "snakeCAseFunc"_L1 << "snakeCAseFunc"_L1; +} + +void TestModifyFunction::testSnakeCaseRenaming() +{ + QFETCH(QLatin1StringView, name); + QFETCH(QLatin1StringView, expected); + + const QString actual = AbstractMetaBuilder::getSnakeCaseName(name); + QCOMPARE(actual, expected); +} + +QTEST_APPLESS_MAIN(TestModifyFunction) diff --git a/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.h b/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.h new file mode 100644 index 000000000..8a4f5d826 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmodifyfunction.h @@ -0,0 +1,26 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTABSTRACTMETACLASS_H +#define TESTABSTRACTMETACLASS_H + +#include <QtCore/QObject> + +class TestModifyFunction : public QObject +{ + Q_OBJECT + private slots: + void testOwnershipTransfer(); + void testWithApiVersion(); + void testAllowThread(); + void testRenameArgument_data(); + void testRenameArgument(); + void invalidateAfterUse(); + void testGlobalFunctionModification(); + void testScopedModifications_data(); + void testScopedModifications(); + void testSnakeCaseRenaming_data(); + void testSnakeCaseRenaming(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp b/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp new file mode 100644 index 000000000..1cf4c8e0f --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testmultipleinheritance.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <typesystem.h> + +void TestMultipleInheritance::testVirtualClass() +{ + const char cppCode[] = "\ + struct A {\n\ + virtual ~A();\n\ + virtual void theBug();\n\ + };\n\ + struct B {\n\ + virtual ~B();\n\ + };\n\ + struct C : A, B {\n\ + };\n\ + struct D : C {\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <object-type name='A' />\n\ + <object-type name='B' />\n\ + <object-type name='C' />\n\ + <object-type name='D' />\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 4); + + const auto classD = AbstractMetaClass::findClass(classes, "D"); + bool functionFound = false; + for (const auto &f : classD->functions()) { + if (f->name() == u"theBug") { + functionFound = true; + break; + } + } + QVERIFY(functionFound); + +} + +QTEST_APPLESS_MAIN(TestMultipleInheritance) diff --git a/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.h b/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.h new file mode 100644 index 000000000..ec9935305 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testmultipleinheritance.h @@ -0,0 +1,18 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTMULTIPLEINHERITANCE_H +#define TESTMULTIPLEINHERITANCE_H + +#include <QtCore/QObject> + +class AbstractMetaBuilder; + +class TestMultipleInheritance : public QObject +{ + Q_OBJECT + private slots: + void testVirtualClass(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp b/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp new file mode 100644 index 000000000..3773e614a --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testnamespace.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testnamespace.h" +#include "testutil.h" +#include <abstractmetalang.h> +#include <abstractmetaenum.h> +#include <typesystem.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void NamespaceTest::testNamespaceMembers() +{ + const char cppCode[] = "\ + namespace Namespace\n\ + {\n\ + enum Option {\n\ + OpZero,\n\ + OpOne\n\ + };\n\ + void foo(Option opt);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <namespace-type name='Namespace'>\n\ + <enum-type name='Option' />\n\ + </namespace-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto ns = AbstractMetaClass::findClass(classes, "Namespace"); + QVERIFY(ns); + auto metaEnum = ns->findEnum(u"Option"_s); + QVERIFY(metaEnum.has_value()); + const auto func = ns->findFunction("foo"); + QVERIFY(func); +} + +void NamespaceTest::testNamespaceInnerClassMembers() +{ + const char cppCode[] = "\ + namespace OuterNamespace\n\ + {\n\ + namespace InnerNamespace {\n\ + struct SomeClass {\n\ + void method();\n\ + };\n\ + };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <namespace-type name='OuterNamespace'>\n\ + <namespace-type name='InnerNamespace'>\n\ + <value-type name='SomeClass'/>\n\ + </namespace-type>\n\ + </namespace-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto ons = AbstractMetaClass::findClass(classes, "OuterNamespace"); + QVERIFY(ons); + const auto ins = AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace"); + QVERIFY(ins); + const auto sc = AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace::SomeClass"); + QVERIFY(sc); + const auto meth = sc->findFunction("method"); + QVERIFY(meth); +} + +QTEST_APPLESS_MAIN(NamespaceTest) + diff --git a/sources/shiboken6/ApiExtractor/tests/testnamespace.h b/sources/shiboken6/ApiExtractor/tests/testnamespace.h new file mode 100644 index 000000000..af46bdea3 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testnamespace.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTNAMESPACE_H +#define TESTNAMESPACE_H + +#include <QtCore/QObject> + +// The class is named 'NamespaceTest' to avoid clashes with Qt COIN using +// '-qtnamespace TestNamespace'. +class NamespaceTest : public QObject +{ + Q_OBJECT + private slots: + void testNamespaceMembers(); + void testNamespaceInnerClassMembers(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp b/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp new file mode 100644 index 000000000..10ca1a0f6 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testnestedtypes.cpp @@ -0,0 +1,115 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testnestedtypes.h" +#include "testutil.h" +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <codesnip.h> +#include <modifications.h> +#include <complextypeentry.h> +#include <primitivetypeentry.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestNestedTypes::testNestedTypesModifications() +{ + const char cppCode[] = "\ + namespace OuterNamespace {\n\ + namespace InnerNamespace {\n\ + struct SomeClass {\n\ + void method() {}\n\ + };\n\ + };\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <namespace-type name='OuterNamespace'>\n\ + <namespace-type name='InnerNamespace'>\n\ + <inject-code class='native'>custom_code1();</inject-code>\n\ + <add-function signature='method()' return-type='OuterNamespace::InnerNamespace::SomeClass'>\n\ + <inject-code class='target'>custom_code2();</inject-code>\n\ + </add-function>\n\ + <object-type name='SomeClass' target-lang-name='RenamedSomeClass'>\n\ + <modify-function signature='method()' remove='all'/>\n\ + </object-type>\n\ + </namespace-type>\n\ + </namespace-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + + const auto ons = AbstractMetaClass::findClass(classes, "OuterNamespace"); + QVERIFY(ons); + + const auto ins = AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace"); + QVERIFY(ins); + QCOMPARE(ins->functions().size(), 1); + QCOMPARE(ins->typeEntry()->codeSnips().size(), 1); + CodeSnip snip = ins->typeEntry()->codeSnips().constFirst(); + QCOMPARE(snip.code().trimmed(), u"custom_code1();"); + + const auto addedFunc = ins->functions().constFirst(); + QVERIFY(addedFunc->isUserAdded()); + QCOMPARE(addedFunc->access(), Access::Public); + QCOMPARE(addedFunc->functionType(), AbstractMetaFunction::NormalFunction); + QCOMPARE(addedFunc->type().minimalSignature(), + u"OuterNamespace::InnerNamespace::SomeClass"); + + QCOMPARE(addedFunc->modifications().size(), 1); + QVERIFY(addedFunc->modifications().constFirst().isCodeInjection()); + snip = addedFunc->modifications().constFirst().snips().constFirst(); + QCOMPARE(snip.code().trimmed(), u"custom_code2();"); + + const auto sc = + AbstractMetaClass::findClass(classes, "OuterNamespace::InnerNamespace::SomeClass"); + QVERIFY(sc); + QCOMPARE(sc->functions().size(), 2); // default constructor and removed method + const auto removedFunc = sc->functions().constLast(); + QVERIFY(removedFunc->isModifiedRemoved()); +} + + +void TestNestedTypes::testDuplicationOfNestedTypes() +{ + const char cppCode[] = "\ + namespace Namespace {\n\ + class SomeClass {};\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <namespace-type name='Namespace'>\n\ + <value-type name='SomeClass'>\n\ + <add-function signature='createSomeClass(Namespace::SomeClass)'/>\n\ + </value-type>\n\ + </namespace-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 2); + const auto nspace = AbstractMetaClass::findClass(classes, "Namespace"); + QVERIFY(nspace); + const auto cls1 = AbstractMetaClass::findClass(classes, "SomeClass"); + QVERIFY(cls1); + const auto cls2 = AbstractMetaClass::findClass(classes, "Namespace::SomeClass"); + QVERIFY(cls2); + QCOMPARE(cls1, cls2); + QCOMPARE(cls1->name(), u"SomeClass"); + QCOMPARE(cls1->qualifiedCppName(), u"Namespace::SomeClass"); + + auto t1 = TypeDatabase::instance()->findType(u"Namespace::SomeClass"_s); + QVERIFY(t1); + auto t2 = TypeDatabase::instance()->findType(u"SomeClass"_s); + QVERIFY(!t2); +} + +QTEST_APPLESS_MAIN(TestNestedTypes) diff --git a/sources/shiboken6/ApiExtractor/tests/testnestedtypes.h b/sources/shiboken6/ApiExtractor/tests/testnestedtypes.h new file mode 100644 index 000000000..544ea05ab --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testnestedtypes.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTNESTEDTYPES_H +#define TESTNESTEDTYPES_H + +#include <QtCore/QObject> + +class TestNestedTypes : public QObject +{ + Q_OBJECT +private slots: + void testNestedTypesModifications(); + void testDuplicationOfNestedTypes(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.cpp b/sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.cpp new file mode 100644 index 000000000..9eef7ec47 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.cpp @@ -0,0 +1,90 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testnumericaltypedef.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <typesystem.h> + +void TestNumericalTypedef::testNumericalTypedef() +{ + const char cppCode[] = "\ + typedef double real;\n\ + void funcDouble(double);\n\ + void funcReal(real);\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='double'/>\n\ + <primitive-type name='real'/>\n\ + <function signature='funcDouble(double)'/>\n\ + <function signature='funcReal(real)'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + QCOMPARE(builder->globalFunctions().size(), 2); + auto funcDouble = builder->globalFunctions().constFirst(); + auto funcReal = builder->globalFunctions().constLast(); + QVERIFY(funcReal); + + if (funcDouble->name() == u"funcReal") + std::swap(funcDouble, funcReal); + + QCOMPARE(funcDouble->minimalSignature(), u"funcDouble(double)"); + QCOMPARE(funcReal->minimalSignature(), u"funcReal(real)"); + + const AbstractMetaType doubleType = funcDouble->arguments().constFirst().type(); + QCOMPARE(doubleType.cppSignature(), u"double"); + QVERIFY(doubleType.isPrimitive()); + QVERIFY(isCppPrimitive(doubleType.typeEntry())); + + const AbstractMetaType realType = funcReal->arguments().constFirst().type(); + QCOMPARE(realType.cppSignature(), u"real"); + QVERIFY(realType.isPrimitive()); + QVERIFY(isCppPrimitive(realType.typeEntry())); +} + +void TestNumericalTypedef::testUnsignedNumericalTypedef() +{ + const char cppCode[] = "\ + typedef unsigned short custom_ushort;\n\ + void funcUnsignedShort(unsigned short);\n\ + void funcUShort(custom_ushort);\n"; + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='short'/>\n\ + <primitive-type name='unsigned short'/>\n\ + <primitive-type name='custom_ushort'/>\n\ + <function signature='funcUnsignedShort(unsigned short)'/>\n\ + <function signature='funcUShort(custom_ushort)'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + QCOMPARE(builder->globalFunctions().size(), 2); + auto funcUnsignedShort = builder->globalFunctions().constFirst(); + auto funcUShort = builder->globalFunctions().constLast(); + + if (funcUnsignedShort->name() == u"funcUShort") + std::swap(funcUnsignedShort, funcUShort); + + QCOMPARE(funcUnsignedShort->minimalSignature(), u"funcUnsignedShort(unsigned short)"); + QCOMPARE(funcUShort->minimalSignature(), u"funcUShort(custom_ushort)"); + + const AbstractMetaType unsignedShortType = funcUnsignedShort->arguments().constFirst().type(); + QCOMPARE(unsignedShortType.cppSignature(), u"unsigned short"); + QVERIFY(unsignedShortType.isPrimitive()); + QVERIFY(isCppPrimitive(unsignedShortType.typeEntry())); + + const AbstractMetaType ushortType = funcUShort->arguments().constFirst().type(); + QCOMPARE(ushortType.cppSignature(), u"custom_ushort"); + QVERIFY(ushortType.isPrimitive()); + QVERIFY(isCppPrimitive(ushortType.typeEntry())); +} + +QTEST_APPLESS_MAIN(TestNumericalTypedef) + diff --git a/sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.h b/sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.h new file mode 100644 index 000000000..32f549836 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testnumericaltypedef.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTNUMERICALTYPEDEF_H +#define TESTNUMERICALTYPEDEF_H + +#include <QtCore/QObject> + +class TestNumericalTypedef : public QObject +{ + Q_OBJECT + private slots: + void testNumericalTypedef(); + void testUnsignedNumericalTypedef(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp b/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp new file mode 100644 index 000000000..99cced09d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testprimitivetypetag.h" +#include "testutil.h" +#include <abstractmetalang.h> +#include <primitivetypeentry.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestPrimitiveTypeTag::testPrimitiveTypeDefaultConstructor() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {};\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <primitive-type name='A' default-constructor='A()'/>\n\ + <object-type name='B'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + + auto typeEntry = TypeDatabase::instance()->findPrimitiveType(u"A"_s); + QVERIFY(typeEntry); + QVERIFY(typeEntry->hasDefaultConstructor()); + QCOMPARE(typeEntry->defaultConstructor(), u"A()"); +} + +QTEST_APPLESS_MAIN(TestPrimitiveTypeTag) + diff --git a/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.h b/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.h new file mode 100644 index 000000000..3a0e05138 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testprimitivetypetag.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTPRIMITIVETYPETAG_H +#define TESTPRIMITIVETYPETAG_H + +#include <QtCore/QObject> + +class TestPrimitiveTypeTag : public QObject +{ + Q_OBJECT + private slots: + void testPrimitiveTypeDefaultConstructor(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp b/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp new file mode 100644 index 000000000..f2e261624 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testrefcounttag.cpp @@ -0,0 +1,84 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testrefcounttag.h" +#include "testutil.h" +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <modifications.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestRefCountTag::testReferenceCountTag() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + void keepObject(B* b);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <object-type name='A'/>\n\ + <object-type name='B'>\n\ + <modify-function signature='keepObject(B*)'>\n\ + <modify-argument index='1'>\n\ + <reference-count action='add'/>\n\ + </modify-argument>\n\ + </modify-function>\n\ + </object-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto func = classB->findFunction("keepObject"); + QVERIFY(func); + const auto refCount = + func->modifications().constFirst().argument_mods().constFirst().referenceCounts().constFirst(); + QCOMPARE(refCount.action, ReferenceCount::Add); +} + +void TestRefCountTag::testWithApiVersion() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + void keepObject(B*, B*);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <object-type name='A'/>\n\ + <object-type name='B'>\n\ + <modify-function signature='keepObject(B*, B*)'>\n\ + <modify-argument index='1' since='0.1'>\n\ + <reference-count action='add'/>\n\ + </modify-argument>\n\ + <modify-argument index='2' since='0.2'>\n\ + <reference-count action='add'/>\n\ + </modify-argument>\n\ + </modify-function>\n\ + </object-type>\n\ + </typesystem>\n"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, + false, u"0.1"_s)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + const auto func = classB->findFunction("keepObject"); + QVERIFY(func); + const auto refCount = + func->modifications().constFirst().argument_mods().constFirst().referenceCounts().constFirst(); + QCOMPARE(refCount.action, ReferenceCount::Add); + + QCOMPARE(func->modifications().size(), 1); +} + + +QTEST_APPLESS_MAIN(TestRefCountTag) + + diff --git a/sources/shiboken6/ApiExtractor/tests/testrefcounttag.h b/sources/shiboken6/ApiExtractor/tests/testrefcounttag.h new file mode 100644 index 000000000..6093c6f7b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testrefcounttag.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTREFCOUNTTAG_H +#define TESTREFCOUNTTAG_H + +#include <QtCore/QObject> + +class TestRefCountTag : public QObject +{ + Q_OBJECT + private slots: + void testReferenceCountTag(); + void testWithApiVersion(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp b/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp new file mode 100644 index 000000000..ae85c5a86 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.cpp @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testreferencetopointer.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <typesystem.h> + +void TestReferenceToPointer::testReferenceToPointerArgument() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {\n\ + void dummy(A*&);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <object-type name='A'/>\n\ + <object-type name='B'/>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + const auto func = classB->findFunction("dummy"); + QVERIFY(func); + QCOMPARE(func->arguments().constFirst().type().minimalSignature(), u"A*&"); +} + +QTEST_APPLESS_MAIN(TestReferenceToPointer) + + diff --git a/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.h b/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.h new file mode 100644 index 000000000..2a7b34807 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testreferencetopointer.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTREFERENCETOPOINTER_H +#define TESTREFERENCETOPOINTER_H + +#include <QtCore/QObject> + +class TestReferenceToPointer : public QObject +{ + Q_OBJECT + private slots: + void testReferenceToPointerArgument(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp b/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp new file mode 100644 index 000000000..2cc82071b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testremovefield.cpp @@ -0,0 +1,76 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testremovefield.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafield.h> +#include <abstractmetafunction.h> +#include <abstractmetatype.h> +#include <abstractmetalang.h> +#include <typesystem.h> + +using namespace Qt::StringLiterals; + +void TestRemoveField::testRemoveField() +{ + const char cppCode[] = "\ + struct A {\n\ + int fieldA;\n\ + int fieldB;\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <primitive-type name='int'/>\n\ + <value-type name='A'>\n\ + <modify-field name='fieldB' remove='all'/>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->fields().size(), 1); + const AbstractMetaField &fieldA = classA->fields().constFirst(); + QCOMPARE(fieldA.name(), u"fieldA"); +} + +// Verify that 'static constexpr' fields are seen as static/const and +// appear fully qualified for function parameter default values. +void TestRemoveField::testConstExprField() +{ + const char cppCode[] = R"( +struct A { + static constexpr int constExprField = 44; + + void f(int iParam=constExprField); +}; +)"; + + const char xmlCode[] = R"( +<typesystem package="Foo"> + <value-type name='A'/> +</typesystem> +)"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto &fields = classA->fields(); + QCOMPARE(fields.size(), 1); + QVERIFY(fields.constFirst().isStatic()); + QVERIFY(fields.constFirst().type().isConstant()); + const auto function = classA->findFunction("f"_L1); + QVERIFY(function); + const auto &arguments = function->arguments(); + QCOMPARE(arguments.size(), 1); + QCOMPARE(arguments.constFirst().defaultValueExpression(), "A::constExprField"_L1); +} + +QTEST_APPLESS_MAIN(TestRemoveField) + + diff --git a/sources/shiboken6/ApiExtractor/tests/testremovefield.h b/sources/shiboken6/ApiExtractor/tests/testremovefield.h new file mode 100644 index 000000000..05912d99e --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testremovefield.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTREMOVEFIELD_H +#define TESTREMOVEFIELD_H + +#include <QtCore/QObject> + +class TestRemoveField : public QObject +{ + Q_OBJECT + private slots: + void testRemoveField(); + void testConstExprField(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp b/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp new file mode 100644 index 000000000..87e318e95 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testremoveimplconv.h" +#include "testutil.h" +#include <QtTest/QTest> +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <complextypeentry.h> + +// When a constructor able to trigger implicity conversions is removed +// it should not appear in the implicity conversion list. +void TestRemoveImplConv::testRemoveImplConv() +{ + const char cppCode[] = "\ + struct A {};\n\ + struct B {};\n\ + struct C {\n\ + C(const A&);\n\ + C(const B&);\n\ + };\n"; + const char xmlCode[] = "\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'/>\n\ + <value-type name='B'/>\n\ + <value-type name='C'>\n\ + <modify-function signature='C(const A&)' remove='all'/>\n\ + </value-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 3); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + const auto classC = AbstractMetaClass::findClass(classes, "C"); + QVERIFY(classC); + const auto implConv = classC->implicitConversions(); + QCOMPARE(implConv.size(), 1); + QCOMPARE(implConv.constFirst()->arguments().constFirst().type().typeEntry(), + classB->typeEntry()); +} + +QTEST_APPLESS_MAIN(TestRemoveImplConv) diff --git a/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.h b/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.h new file mode 100644 index 000000000..d11d30633 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testremoveimplconv.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTREMOVEIMPLCONV_H +#define TESTREMOVEIMPLCONV_H + +#include <QtCore/QObject> + +class TestRemoveImplConv : public QObject +{ +Q_OBJECT +private slots: + void testRemoveImplConv(); +}; + +#endif // TESTREMOVEIMPLCONV_H diff --git a/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp b/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp new file mode 100644 index 000000000..17a069b5e --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.cpp @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testremoveoperatormethod.h" +#include "testutil.h" +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <typesystem.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestRemoveOperatorMethod::testRemoveOperatorMethod() +{ + const char cppCode[] = R"(#include <cstdint> + +struct Char {}; +struct ByteArray {}; +struct String {}; + +struct A { + A& operator>>(char&); + A& operator>>(char*); + A& operator>>(short&); + A& operator>>(unsigned short&); + A& operator>>(int&); + A& operator>>(unsigned int&); + A& operator>>(int64_t&); + A& operator>>(uint64_t&); + A& operator>>(float&); + A& operator>>(double&); + A& operator>>(Char&); + A& operator>>(ByteArray&); + A& operator>>(String&); +}; +)"; + + const char xmlCode[] = "\ + <typesystem package='Foo'>\n\ + <primitive-type name='char'/>\n\ + <primitive-type name='short'/>\n\ + <primitive-type name='unsigned short'/>\n\ + <primitive-type name='int'/>\n\ + <primitive-type name='unsigned int'/>\n\ + <primitive-type name='int64_t'/>\n\ + <primitive-type name='uint64_t'/>\n\ + <primitive-type name='float'/>\n\ + <primitive-type name='double'/>\n\ + <primitive-type name='Char'/>\n\ + <primitive-type name='String'/>\n\ + <value-type name='ByteArray'/>\n\ + <object-type name='A'>\n\ + <modify-function signature='operator>>(char&)' remove='all'/>\n\ + <modify-function signature='operator>>(char*)' remove='all'/>\n\ + <modify-function signature='operator>>(short&)' remove='all'/>\n\ + <modify-function signature='operator>>(unsigned short&)' remove='all'/>\n\ + <modify-function signature='operator>>(int&)' remove='all'/>\n\ + <modify-function signature='operator>>(unsigned int&)' remove='all'/>\n\ + <modify-function signature='operator>>(int64_t&)' remove='all'/>\n\ + <modify-function signature='operator>>(uint64_t&)' remove='all'/>\n\ + <modify-function signature='operator>>(float&)' remove='all'/>\n\ + <modify-function signature='operator>>(double&)' remove='all'/>\n\ + <modify-function signature='operator>>(Char&)' remove='all'/>\n\ + <modify-function signature='operator>>(String&)' remove='all'/>\n\ + </object-type>\n\ + </typesystem>\n"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->functions().size(), 14); + QStringList removedSignatures; + removedSignatures.append(u"operator>>(char&)"_s); + removedSignatures.append(u"operator>>(char*)"_s); + removedSignatures.append(u"operator>>(short&)"_s); + removedSignatures.append(u"operator>>(unsigned short&)"_s); + removedSignatures.append(u"operator>>(int&)"_s); + removedSignatures.append(u"operator>>(unsigned int&)"_s); + removedSignatures.append(u"operator>>(int64_t&)"_s); + removedSignatures.append(u"operator>>(uint64_t&)"_s); + removedSignatures.append(u"operator>>(float&)"_s); + removedSignatures.append(u"operator>>(double&)"_s); + removedSignatures.append(u"operator>>(Char&)"_s); + removedSignatures.append(u"operator>>(String&)"_s); + auto notRemoved = classA->functions().size(); + for (const auto &f : classA->functions()) { + QCOMPARE(f->isModifiedRemoved(), bool(removedSignatures.contains(f->minimalSignature()))); + notRemoved -= int(f->isModifiedRemoved()); + } + QCOMPARE(notRemoved, 2); +} + +QTEST_APPLESS_MAIN(TestRemoveOperatorMethod) + diff --git a/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.h b/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.h new file mode 100644 index 000000000..6ec335e0c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testremoveoperatormethod.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTREMOVEOPERATORMETHOD_H +#define TESTREMOVEOPERATORMETHOD_H + +#include <QtCore/QObject> + +class TestRemoveOperatorMethod : public QObject +{ + Q_OBJECT + private slots: + void testRemoveOperatorMethod(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp b/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp new file mode 100644 index 000000000..67ebcc606 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testresolvetype.cpp @@ -0,0 +1,281 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testresolvetype.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetaenum.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <complextypeentry.h> +#include <enumtypeentry.h> +#include <primitivetypeentry.h> +#include <typedatabase.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestResolveType::initTestCase() +{ + // For enum lookup in testFixDefaultArguments() + AbstractMetaBuilder::setCodeModelTestMode(true); +} + +void TestResolveType::testResolveReturnTypeFromParentScope() +{ + const char cppCode[] = "\n\ + namespace A {\n\ + struct B {\n\ + struct C {};\n\ + };\n\ + struct D : public B::C {\n\ + C* foo = 0;\n\ + C* method();\n\ + };\n\ + };"; + const char xmlCode[] = R"XML( + <typesystem package='Foo'> + <namespace-type name='A'> + <value-type name='B'> + <value-type name='C'/> + </value-type> + <value-type name='D'/> + </namespace-type> + </typesystem>)XML"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classD = AbstractMetaClass::findClass(classes, "A::D"); + QVERIFY(classD); + const auto meth = classD->findFunction("method"); + QVERIFY(meth); + QVERIFY(meth); +} + +// Helper classes and functions for testing default value fixing. +// Put the AbstractMetaBuilder into test fixture struct to avoid having +// to re-parse for each data row. + +struct DefaultValuesFixture +{ + std::shared_ptr<AbstractMetaBuilder> builder; + + AbstractMetaType intType; + AbstractMetaType stringType; + AbstractMetaType classType; + AbstractMetaType listType; + AbstractMetaType enumType; + AbstractMetaClassCPtr klass{}; +}; + +Q_DECLARE_METATYPE(DefaultValuesFixture) +Q_DECLARE_METATYPE(AbstractMetaType) + +static int populateDefaultValuesFixture(DefaultValuesFixture *fixture) +{ + static const char cppCode[] =R"( +#include <string> +#include <list> + +namespace Namespace { +class Test +{ +public: + enum Enum { enumValue1, enumValue2 }; + + explicit Test(int x = INT_FIELD_1); + explicit Test(const std::string &t = std::string(CHAR_FIELD_1)); + + static void listFunc(std::list<Test> list = std::list<Test>()); + + static const int INT_FIELD_1 = 42; + static const char *CHAR_FIELD_1; + static const Enum DefaultValue = enumValue1; +}; +} // Namespace +)"; + static const char xmlCode[] = R"( +<typesystem package="Foo"> + <namespace-type name='Namespace'> + <value-type name='Test'> + <enum-type name='Enum'/> + </value-type> + </namespace-type> + <container-type name="std::list" type="list"/> +</typesystem> +)"; + + fixture->builder.reset(TestUtil::parse(cppCode, xmlCode, false)); + if (!fixture->builder) + return -1; + + for (const auto &klass : fixture->builder->classes()) { + if (klass->name() == u"Test") { + fixture->klass = klass; + break; + } + } + if (!fixture->klass) + return -2; + + fixture->classType = AbstractMetaType(fixture->klass->typeEntry()); + fixture->classType.decideUsagePattern(); + + for (const auto &f : fixture->klass->findFunctions("Test")) { + if (f->functionType() == AbstractMetaFunction::ConstructorFunction + && f->arguments().size() == 1) { + const auto type = f->arguments().constFirst().type(); + if (type.name() == u"int") + fixture->intType = type; + else + fixture->stringType = type; + } + } + if (fixture->intType.isVoid() || fixture->stringType.isVoid()) + return -3; + + auto listFunc = fixture->klass->findFunction("listFunc"); + if (!listFunc || listFunc->arguments().size() != 1) + return -3; + fixture->listType = listFunc->arguments().constFirst().type(); + + fixture->enumType = AbstractMetaType(fixture->klass->enums().constFirst().typeEntry()); + fixture->enumType.decideUsagePattern(); + + return 0; +} + +void TestResolveType::testFixDefaultArguments_data() +{ + DefaultValuesFixture fixture; + const int setupOk = populateDefaultValuesFixture(&fixture); + + QTest::addColumn<DefaultValuesFixture>("fixture"); + QTest::addColumn<int>("setupOk"); // To verify setup + QTest::addColumn<AbstractMetaType>("metaType"); // Type and parameters for fixup + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("expected"); + + QTest::newRow("int") << fixture << setupOk + << fixture.intType << "1" << "1"; + QTest::newRow("int-macro") << fixture << setupOk + << fixture.intType << "GL_MACRO" << "GL_MACRO"; + QTest::newRow("int-enum") << fixture << setupOk + << fixture.intType << "enumValue1" << "Namespace::Test::Enum::enumValue1"; + + // Test expansion of container types + QString expected = u"std::list<Namespace::Test>()"_s; + QTest::newRow("list") + << fixture << setupOk << fixture.listType + << expected << expected; + QTest::newRow("partially qualified list") + << fixture << setupOk << fixture.listType + << "std::list<Test>()" << expected; + + // Test field expansion + expected = u"Namespace::Test::INT_FIELD_1"_s; + QTest::newRow("qualified class field") + << fixture << setupOk << fixture.intType + << expected << expected; + QTest::newRow("partially qualified class field") + << fixture << setupOk << fixture.intType + << "Test::INT_FIELD_1" << expected; + QTest::newRow("unqualified class field") + << fixture << setupOk << fixture.intType + << "INT_FIELD_1" << expected; + + // Test field expansion when constructing some class + expected = u"QLatin1String(Namespace::Test::CHAR_FIELD_1)"_s; + QTest::newRow("class from qualified class field") + << fixture << setupOk << fixture.classType + << expected << expected; + QTest::newRow("class from partially qualified class field") + << fixture << setupOk << fixture.classType + << "QLatin1String(Test::CHAR_FIELD_1)" << expected; + QTest::newRow("class from unqualified class field") + << fixture << setupOk << fixture.classType + << "QLatin1String(CHAR_FIELD_1)" << expected; + + // Test field expansion when constructing class itself + expected = u"Namespace::Test(Namespace::Test::CHAR_FIELD_1)"_s; + QTest::newRow("self from qualified class field") + << fixture << setupOk << fixture.classType + << expected << expected; + QTest::newRow("self from partially qualified class field") + << fixture << setupOk << fixture.classType + << "Test(Test::CHAR_FIELD_1)" << expected; + QTest::newRow("self from unqualified class field") + << fixture << setupOk << fixture.classType + << "Test(CHAR_FIELD_1)" << expected; + + // Test enum expansion when constructing class itself + expected = u"Namespace::Test(Namespace::Test::Enum::enumValue1)"_s; + QTest::newRow("self from qualified enum") + << fixture << setupOk << fixture.classType + << expected << expected; + QTest::newRow("self from enum") + << fixture << setupOk << fixture.classType + << "Test(enumValue1)" << expected; + + // Don't qualify fields to "Test::Enum::DefaultValue" + QTest::newRow("enum from static field") + << fixture << setupOk << fixture.enumType + << "DefaultValue" << u"Namespace::Test::DefaultValue"_s; +} + +void TestResolveType::testFixDefaultArguments() +{ + QFETCH(DefaultValuesFixture, fixture); + QFETCH(int, setupOk); + QFETCH(AbstractMetaType, metaType); + QFETCH(QString, input); + QFETCH(QString, expected); + QCOMPARE(setupOk, 0); + const QString actual = fixture.builder->fixDefaultValue(input, metaType, fixture.klass); + QCOMPARE(actual, expected); +} + +// Verify that the typedefs of the C++ 11 integer types (int32_t, ...) +// are seen by the C++ parser, otherwise they are handled as unknown +// primitive types, causing invalid code to be generated. +// (see BuilderPrivate::visitHeader(), +// sources/shiboken6/ApiExtractor/clangparser/clangbuilder.cpp). +void TestResolveType::testCppTypes() +{ + static const char cppCode[] =R"( +#include <cstdint> + +class Test +{ +public: + explicit Test(int32_t v); +}; +)"; + static const char xmlCode[] = R"( +<typesystem package="Foo"> + <value-type name='Test'/> + <primitive-type name='int32_t'/> +</typesystem> +)"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto testClass = AbstractMetaClass::findClass(classes, "Test"); + QVERIFY(testClass); + + auto *tdb = TypeDatabase::instance(); + auto int32TEntry = tdb->findType(u"int32_t"_s); + QVERIFY2(int32TEntry, "int32_t not found"); + QVERIFY(int32TEntry->isPrimitive()); + auto int32T = std::static_pointer_cast<const PrimitiveTypeEntry>(int32TEntry); + auto basicType = basicReferencedTypeEntry(int32T); + QVERIFY2(basicType != int32T, + "Typedef for int32_t not found. Check the system include paths."); +} + +QTEST_APPLESS_MAIN(TestResolveType) diff --git a/sources/shiboken6/ApiExtractor/tests/testresolvetype.h b/sources/shiboken6/ApiExtractor/tests/testresolvetype.h new file mode 100644 index 000000000..a07855eab --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testresolvetype.h @@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTRESOLVETYPE_H +#define TESTRESOLVETYPE_H + +#include <QtCore/QObject> + +class TestResolveType : public QObject +{ + Q_OBJECT + private slots: + void initTestCase(); + + void testResolveReturnTypeFromParentScope(); + void testFixDefaultArguments_data(); + void testFixDefaultArguments(); + void testCppTypes(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp b/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp new file mode 100644 index 000000000..f4eecff2c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testreverseoperators.cpp @@ -0,0 +1,129 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testreverseoperators.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <typesystem.h> +#include <clangparser/compilersupport.h> + +#include <algorithm> + +void TestReverseOperators::testReverseSum() +{ + const char cppCode[] = "struct A {\n\ + A& operator+(int);\n\ + };\n\ + A& operator+(int, const A&);"; + const char xmlCode[] = "\n\ + <typesystem package=\"Foo\">\n\ + <primitive-type name='int' />\n\ + <value-type name='A' />\n\ + </typesystem>"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->functions().size(), 4); + + AbstractMetaFunctionCPtr reverseOp; + AbstractMetaFunctionCPtr normalOp; + for (const auto &func : classA->functions()) { + if (func->name() == u"operator+") { + if (func->isReverseOperator()) + reverseOp = func; + else + normalOp = func; + } + } + + QVERIFY(normalOp); + QVERIFY(!normalOp->isReverseOperator()); + QCOMPARE(normalOp->arguments().size(), 1); + QVERIFY(reverseOp); + QVERIFY(reverseOp->isReverseOperator()); + QCOMPARE(reverseOp->arguments().size(), 1); +} + +void TestReverseOperators::testReverseSumWithAmbiguity() +{ + const char cppCode[] = "\n\ + struct A { A operator+(int); };\n\ + A operator+(int, const A&);\n\ + struct B {};\n\ + B operator+(const A&, const B&);\n\ + B operator+(const B&, const A&);\n\ + "; + const char xmlCode[] = "\n\ + <typesystem package=\"Foo\">\n\ + <primitive-type name='int' />\n\ + <value-type name='A' />\n\ + <value-type name='B' />\n\ + </typesystem>"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QCOMPARE(classA->functions().size(), 4); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + QCOMPARE(classB->functions().size(), 4); + + AbstractMetaFunctionCPtr reverseOp; + AbstractMetaFunctionCPtr normalOp; + for (const auto &func : classB->functions()) { + if (func->name() == u"operator+") { + if (func->isReverseOperator()) + reverseOp = func; + else + normalOp = func; + } + } + QVERIFY(normalOp); + QVERIFY(!normalOp->isReverseOperator()); + QCOMPARE(normalOp->arguments().size(), 1); + QCOMPARE(normalOp->minimalSignature(), u"operator+(B,A)"); + QVERIFY(reverseOp); + QVERIFY(reverseOp->isReverseOperator()); + QCOMPARE(reverseOp->arguments().size(), 1); + QCOMPARE(reverseOp->minimalSignature(), u"operator+(A,B)"); +} + +void TestReverseOperators::testSpaceshipOperator() +{ + const char cppCode[] = R"( + class Test { + public: + explicit Test(int v); + int operator<=>(const Test &rhs) const = default; + };)"; + const char xmlCode[] = R"( + <typesystem package="Foo"> + <value-type name='Test'/> + </typesystem>)"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false, + {}, {}, LanguageLevel::Cpp20)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + const auto testClass = AbstractMetaClass::findClass(classes, "Test"); + QVERIFY(testClass); + const auto &functions = testClass->functions(); + // 6 operators should be synthesized + const auto count = std::count_if(functions.cbegin(), functions.cend(), + [](const AbstractMetaFunctionCPtr &f) { + return f->isComparisonOperator(); + }); + QCOMPARE(count, 6); +} + +QTEST_APPLESS_MAIN(TestReverseOperators) + diff --git a/sources/shiboken6/ApiExtractor/tests/testreverseoperators.h b/sources/shiboken6/ApiExtractor/tests/testreverseoperators.h new file mode 100644 index 000000000..fb8d97c97 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testreverseoperators.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTREVERSEOPERATORS_H +#define TESTREVERSEOPERATORS_H +#include <QtCore/QObject> + +class TestReverseOperators : public QObject +{ + Q_OBJECT +private slots: + void testReverseSum(); + void testReverseSumWithAmbiguity(); + void testSpaceshipOperator(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp b/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp new file mode 100644 index 000000000..ea37c6255 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testtemplates.cpp @@ -0,0 +1,628 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testtemplates.h" +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafield.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <abstractmetatype.h> +#include <complextypeentry.h> +#include <containertypeentry.h> + +#include <qtcompat.h> + +#include <QtCore/QTemporaryFile> +#include <QtCore/QTextStream> +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestTemplates::testTemplateWithNamespace() +{ + const char cppCode[] = R"CPP( +template<typename T> struct QList {}; +struct Url { + void name(); +}; +namespace Internet { + struct Url{}; + struct Bookmarks { + QList<Url> list(); + }; +}; +)CPP"; + + const char xmlCode0[] = R"XML( +<typesystem package='Package.Network'> + <value-type name='Url'/> +</typesystem>)XML"; + + QTemporaryFile file; + QVERIFY(file.open()); + file.write(xmlCode0); + file.close(); + + QString xmlCode1 = QString::fromLatin1(R"XML( +<typesystem package='Package.Internet'> + <load-typesystem name='%1' generate='no'/> + <container-type name='QList' type='list'/> + <namespace-type name='Internet' generate='no'> + <value-type name='Url'/> + <value-type name='Bookmarks'/> + </namespace-type> +</typesystem>)XML").arg(file.fileName()); + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, qPrintable(xmlCode1), false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + + const auto classB = AbstractMetaClass::findClass(classes, "Bookmarks"); + QVERIFY(classB); + const auto func = classB->findFunction("list"); + QVERIFY(func); + AbstractMetaType funcType = func->type(); + QVERIFY(!funcType.isVoid()); + QCOMPARE(funcType.cppSignature(), u"QList<Internet::Url>"); +} + +void TestTemplates::testTemplateOnContainers() +{ + const char cppCode[] = R"CPP( +struct Base {}; +template<typename T> struct QList {}; +namespace Namespace { + enum SomeEnum { E1, E2 }; + template<SomeEnum type> struct A { + A<type> foo(const QList<A<type> >& a); + }; + typedef A<E1> B; +} +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package="Package"> + <container-type name='QList' type='list'/> + <namespace-type name='Namespace'> + <enum-type name='SomeEnum'/> + <object-type name='A' generate='no'/> + <object-type name='B'/> + </namespace-type> + <object-type name='Base'/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + QVERIFY(!classB->baseClass()); + QVERIFY(classB->baseClassName().isEmpty()); + const auto func = classB->findFunction("foo"); + QVERIFY(func); + AbstractMetaType argType = func->arguments().constFirst().type(); + QCOMPARE(argType.instantiations().size(), 1); + QCOMPARE(argType.typeEntry()->qualifiedCppName(), u"QList"); + + const AbstractMetaType &instance1 = argType.instantiations().constFirst(); + QCOMPARE(instance1.instantiations().size(), 1); + QCOMPARE(instance1.typeEntry()->qualifiedCppName(), u"Namespace::A"); + + const AbstractMetaType &instance2 = instance1.instantiations().constFirst(); + QCOMPARE(instance2.instantiations().size(), 0); + QCOMPARE(instance2.typeEntry()->qualifiedCppName(), u"Namespace::E1"); +} + +void TestTemplates::testTemplateValueAsArgument() +{ + const char cppCode[] = R"CPP( +template<typename T> struct List {}; +void func(List<int> arg) {} +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Package'> + <primitive-type name='int'/> + <container-type name='List' type='list'/> + <function signature='func(List<int>)'/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + const auto globalFuncs = builder->globalFunctions(); + QCOMPARE(globalFuncs.size(), 1); + + const auto func = globalFuncs.constFirst(); + QCOMPARE(func->minimalSignature(), u"func(List<int>)"); + QCOMPARE(func->arguments().constFirst().type().cppSignature(), + u"List<int>"); +} + +void TestTemplates::testTemplatePointerAsArgument() +{ + const char cppCode[] = R"CPP( +template<typename T> struct List {}; +void func(List<int>* arg) {} +)CPP"; + + const char xmlCode[] = R"XML( + <typesystem package='Package'> + <primitive-type name='int'/> + <container-type name='List' type='list'/> + <function signature='func(List<int>*)'/> + </typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaFunctionCList globalFuncs = builder->globalFunctions(); + QCOMPARE(globalFuncs.size(), 1); + + const auto func = globalFuncs.constFirst(); + QCOMPARE(func->minimalSignature(), u"func(List<int>*)"); + QCOMPARE(func->arguments().constFirst().type().cppSignature(), + u"List<int> *"); +} + +void TestTemplates::testTemplateReferenceAsArgument() +{ + const char cppCode[] = R"CPP( +template<typename T> struct List {}; +void func(List<int>& arg) {} + )CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Package'> + <primitive-type name='int'/> + <container-type name='List' type='list'/> + <function signature='func(List<int>&)'/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + const auto globalFuncs = builder->globalFunctions(); + QCOMPARE(globalFuncs.size(), 1); + + const auto func = globalFuncs.constFirst(); + QCOMPARE(func->minimalSignature(), u"func(List<int>&)"); + QCOMPARE(func->arguments().constFirst().type().cppSignature(), + u"List<int> &"); +} + +void TestTemplates::testTemplateParameterFixup() +{ + const char cppCode[] = R"CPP( +template<typename T> +struct List { + struct Iterator {}; + void append(List l); + void erase(List::Iterator it); +}; +)CPP"; + + const char xmlCode[] = R"XML( + <typesystem package='Package'> + <container-type name='List' type='list'> + <value-type name='Iterator'/> + </container-type> + </typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + const AbstractMetaClassList templates = builder->templates(); + + QCOMPARE(templates.size(), 1); + AbstractMetaClassCPtr list = templates.constFirst(); + // Verify that the parameter of "void append(List l)" gets fixed to "List<T>" + const auto append = list->findFunction("append"); + QVERIFY(append); + QCOMPARE(append->arguments().size(), 1); + QCOMPARE(append->arguments().at(0).type().cppSignature(), u"List<T>"); + // Verify that the parameter of "void erase(Iterator)" is not modified + const auto erase = list->findFunction("erase"); + QVERIFY(erase); + QCOMPARE(erase->arguments().size(), 1); + QCOMPARE(erase->arguments().at(0).type().cppSignature(), u"List::Iterator"); +} + +void TestTemplates::testInheritanceFromContainterTemplate() +{ + const char cppCode[] = R"CPP( +template<typename T> +struct ListContainer { + inline void push_front(const T& t); + inline T& front(); +}; +struct FooBar {}; +struct FooBars : public ListContainer<FooBar> {}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Package'> + <container-type name='ListContainer' type='list'/> + <value-type name='FooBar'/> + <value-type name='FooBars'> + <modify-function signature='push_front(FooBar)' remove='all'/> + <modify-function signature='front()' remove='all'/> + </value-type> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + AbstractMetaClassList templates = builder->templates(); + QCOMPARE(classes.size(), 2); + QCOMPARE(templates.size(), 1); + + const auto foobars = AbstractMetaClass::findClass(classes, "FooBars"); + QCOMPARE(foobars->functions().size(), 4); + + AbstractMetaClassCPtr lc = templates.constFirst(); + QCOMPARE(lc->functions().size(), 2); +} + +void TestTemplates::testTemplateInheritanceMixedWithForwardDeclaration() +{ + const char cppCode[] = R"CPP( +enum SomeEnum { E1, E2 }; +template<SomeEnum type> struct Future; +template<SomeEnum type> +struct A { + A(); + void method(); + friend struct Future<type>; +}; +typedef A<E1> B; +template<SomeEnum type> struct Future {}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Package'> + <enum-type name='SomeEnum'/> + <value-type name='A' generate='no'/> + <value-type name='B'/> + <value-type name='Future' generate='no'/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + QVERIFY(!classB->baseClass()); + QVERIFY(classB->baseClassName().isEmpty()); + // 3 functions: simple constructor, copy constructor and "method()". + QCOMPARE(classB->functions().size(), 3); +} + +void TestTemplates::testTemplateInheritanceMixedWithNamespaceAndForwardDeclaration() +{ + const char cppCode[] = R"CPP( +namespace Namespace { +enum SomeEnum { E1, E2 }; +template<SomeEnum type> struct Future; +template<SomeEnum type> +struct A { + A(); + void method(); + friend struct Future<type>; +}; +typedef A<E1> B; +template<SomeEnum type> struct Future {}; +}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Package'> + <namespace-type name='Namespace'> + <enum-type name='SomeEnum'/> + <value-type name='A' generate='no'/> + <value-type name='B'/> + <value-type name='Future' generate='no'/> + </namespace-type> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + + const auto classB = AbstractMetaClass::findClass(classes, "Namespace::B"); + QVERIFY(classB); + QVERIFY(!classB->baseClass()); + QVERIFY(classB->baseClassName().isEmpty()); + // 3 functions: simple constructor, copy constructor and "method()". + QCOMPARE(classB->functions().size(), 3); +} + +void TestTemplates::testTypedefOfInstantiationOfTemplateClass() +{ + const char cppCode[] = R"CPP( +namespace NSpace { +enum ClassType { + TypeOne +}; +template<ClassType CLASS_TYPE> +struct BaseTemplateClass { + inline ClassType getClassType() const { return CLASS_TYPE; } +}; +typedef BaseTemplateClass<TypeOne> TypeOneClass; +} +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Package'> + <namespace-type name='NSpace'> + <enum-type name='ClassType'/> + <object-type name='BaseTemplateClass' generate='no'/> + <object-type name='TypeOneClass'/> + </namespace-type> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 3); + + const auto base = AbstractMetaClass::findClass(classes, "BaseTemplateClass"); + QVERIFY(base); + const auto one = AbstractMetaClass::findClass(classes, "TypeOneClass"); + QVERIFY(one); + QCOMPARE(one->templateBaseClass(), base); + QCOMPARE(one->functions().size(), base->functions().size()); + QVERIFY(one->isTypeDef()); + auto oneType = one->typeEntry(); + auto baseType = base->typeEntry(); + QCOMPARE(oneType->baseContainerType(), baseType); + QCOMPARE(one->baseClassNames(), QStringList(u"NSpace::BaseTemplateClass<NSpace::TypeOne>"_s)); + + QVERIFY(one->hasTemplateBaseClassInstantiations()); + AbstractMetaTypeList instantiations = one->templateBaseClassInstantiations(); + QCOMPARE(instantiations.size(), 1); + const AbstractMetaType &inst = instantiations.constFirst(); + QVERIFY(!inst.isEnum()); + QVERIFY(!inst.typeEntry()->isEnum()); + QVERIFY(inst.typeEntry()->isEnumValue()); + QCOMPARE(inst.cppSignature(), u"NSpace::TypeOne"); +} + +void TestTemplates::testContainerTypeIncompleteArgument() +{ + const char cppCode[] = R"CPP( +template<typename T> +class Vector { + void method(const Vector& vector); + Vector otherMethod(); +}; +template <typename T> +void Vector<T>::method(const Vector<T>& vector) {} +template <typename T> +Vector<T> Vector<T>::otherMethod() { return Vector<T>(); } +typedef Vector<int> IntVector; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <container-type name='Vector' type='vector'/> + <value-type name='IntVector'/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, true)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + QCOMPARE(classes.size(), 1); + + const auto vector = AbstractMetaClass::findClass(classes, "IntVector"); + QVERIFY(vector); + auto baseContainer = vector->typeEntry()->baseContainerType(); + QVERIFY(baseContainer); + QCOMPARE(reinterpret_cast<const ContainerTypeEntry*>(baseContainer.get())->containerKind(), + ContainerTypeEntry::ListContainer); + QCOMPARE(vector->functions().size(), 4); + + const auto method = vector->findFunction("method"); + QVERIFY(method); + QCOMPARE(method->signature(), u"method(const Vector<int> & vector)"); + + const auto otherMethod = vector->findFunction("otherMethod"); + QVERIFY(otherMethod); + QCOMPARE(otherMethod->signature(), u"otherMethod()"); + QVERIFY(!otherMethod->type().isVoid()); + QCOMPARE(otherMethod->type().cppSignature(), u"Vector<int>"); +} + +void TestTemplates::testNonTypeTemplates() +{ + // PYSIDe-1296, functions with non type templates parameters. + const char cppCode[] = R"CPP( +template <class T, int Size> +class Array { + T array[Size]; +}; + +Array<int, 2> foo(); + +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <container-type name='Array' type='vector'/> + <function signature="foo()"/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, true)); + QVERIFY(builder); + auto functions = builder->globalFunctions(); + QCOMPARE(functions.size(), 1); + auto foo = functions.constFirst(); + QCOMPARE(foo->name(), u"foo"); + QCOMPARE(foo->type().name(), u"Array"); +} + +// Perform checks on template inheritance; a typedef of a template class +// should result in rewritten types. +void TestTemplates::testTemplateTypeDefs_data() +{ + QTest::addColumn<QString>("cpp"); + QTest::addColumn<QString>("xml"); + + const char optionalClassDef[] = R"CPP( +template<class T> // Some value type similar to std::optional +class Optional { +public: + T value() const { return m_value; } + operator bool() const { return m_success; } + + T m_value; + bool m_success = false; +}; +)CPP"; + + const char xmlPrefix[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <primitive-type name='bool'/> +)XML"; + + const char xmlOptionalDecl[] = "<value-type name='Optional' generate='no'/>\n"; + const char xmlOptionalIntDecl[] = "<value-type name='IntOptional'/>\n"; + const char xmlPostFix[] = "</typesystem>\n"; + + // Flat, global namespace + QString cpp; + QTextStream(&cpp) << optionalClassDef + << "typedef Optional<int> IntOptional;\n"; + QString xml; + QTextStream(&xml) << xmlPrefix << xmlOptionalDecl << xmlOptionalIntDecl + << "<typedef-type name='XmlIntOptional' source='Optional<int>'/>" + << xmlPostFix; + QTest::newRow("global-namespace") + << cpp << xml; + + // Typedef from namespace Std + cpp.clear(); + QTextStream(&cpp) << "namespace Std {\n" << optionalClassDef << "}\n" + << "typedef Std::Optional<int> IntOptional;\n"; + xml.clear(); + QTextStream(&xml) << xmlPrefix + << "<namespace-type name='Std'>\n" << xmlOptionalDecl + << "</namespace-type>\n" << xmlOptionalIntDecl + << "<typedef-type name='XmlIntOptional' source='Std::Optional<int>'/>" + << xmlPostFix; + QTest::newRow("namespace-Std") + << cpp << xml; + + // Typedef from nested class + cpp.clear(); + QTextStream(&cpp) << "class Outer {\npublic:\n" << optionalClassDef << "\n};\n" + << "typedef Outer::Optional<int> IntOptional;\n"; + xml.clear(); + QTextStream(&xml) << xmlPrefix + << "<object-type name='Outer'>\n" << xmlOptionalDecl + << "</object-type>\n" << xmlOptionalIntDecl + << "<typedef-type name='XmlIntOptional' source='Outer::Optional<int>'/>" + << xmlPostFix; + QTest::newRow("nested-class") + << cpp << xml; +} + +void TestTemplates::testTemplateTypeDefs() +{ + QFETCH(QString, cpp); + QFETCH(QString, xml); + + const QByteArray cppBa = cpp.toLocal8Bit(); + const QByteArray xmlBa = xml.toLocal8Bit(); + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppBa.constData(), xmlBa.constData(), true)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + + const auto optional = AbstractMetaClass::findClass(classes, "Optional"); + QVERIFY(optional); + + // Find the typedef'ed class + const auto optionalInt = AbstractMetaClass::findClass(classes, "IntOptional"); + QVERIFY(optionalInt); + QCOMPARE(optionalInt->templateBaseClass(), optional); + + // Find the class typedef'ed in the typesystem XML + const auto xmlOptionalInt = AbstractMetaClass::findClass(classes, "XmlIntOptional"); + QVERIFY(xmlOptionalInt); + QCOMPARE(xmlOptionalInt->templateBaseClass(), optional); + + // Check whether the value() method now has an 'int' return + const auto valueMethod = optionalInt->findFunction("value"); + QVERIFY(valueMethod); + QCOMPARE(valueMethod->type().cppSignature(), u"int"); + + // ditto for typesystem XML + const auto xmlValueMethod = xmlOptionalInt->findFunction("value"); + QVERIFY(xmlValueMethod); + QCOMPARE(xmlValueMethod->type().cppSignature(), u"int"); + + // Check whether the m_value field is of type 'int' + const auto valueField = optionalInt->findField(u"m_value"); + QVERIFY(valueField.has_value()); + QCOMPARE(valueField->type().cppSignature(), u"int"); + + // ditto for typesystem XML + const auto xmlValueField = + xmlOptionalInt->findField(u"m_value"); + QVERIFY(xmlValueField.has_value()); + QCOMPARE(xmlValueField->type().cppSignature(), u"int"); +} + +void TestTemplates::testTemplateTypeAliases() +{ + // Model Qt 6's "template<typename T> using QList = QVector<T>" + const char cppCode[] = R"CPP( +template<typename T> +class Container1 { }; + +template<typename T> +using Container2 = Container1<T>; + +class Test +{ +public: + Container2<int> m_intContainer; +}; + +class Derived : public Container2<int> +{ +public: +}; +)CPP"; + + const char xmlCode[] = R"XML( +<typesystem package='Foo'> + <primitive-type name='int'/> + <value-type name='Container1'/> + <value-type name='Derived'/> + <object-type name='Test'/> +</typesystem>)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, true)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + const auto testClass = AbstractMetaClass::findClass(classes, "Test"); + QVERIFY(testClass); + + auto fields = testClass->fields(); + QCOMPARE(fields.size(), 1); + auto fieldType = testClass->fields().at(0).type(); + QCOMPARE(fieldType.name(), u"Container1"); + QCOMPARE(fieldType.instantiations().size(), 1); + + const auto derived = AbstractMetaClass::findClass(classes, "Derived"); + QVERIFY(derived); + auto base = derived->templateBaseClass(); + QVERIFY(base); + QCOMPARE(base->name(), u"Container1"); +} + +QTEST_APPLESS_MAIN(TestTemplates) diff --git a/sources/shiboken6/ApiExtractor/tests/testtemplates.h b/sources/shiboken6/ApiExtractor/tests/testtemplates.h new file mode 100644 index 000000000..36800f723 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testtemplates.h @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTTEMPLATES_H +#define TESTTEMPLATES_H + +#include <QtCore/QObject> + +class TestTemplates : public QObject +{ + Q_OBJECT +private slots: + void testTemplateOnContainers(); + void testTemplateWithNamespace(); + void testTemplateValueAsArgument(); + void testTemplatePointerAsArgument(); + void testTemplateReferenceAsArgument(); + void testTemplateParameterFixup(); + void testInheritanceFromContainterTemplate(); + void testTemplateInheritanceMixedWithForwardDeclaration(); + void testTemplateInheritanceMixedWithNamespaceAndForwardDeclaration(); + void testTypedefOfInstantiationOfTemplateClass(); + void testContainerTypeIncompleteArgument(); + void testNonTypeTemplates(); + void testTemplateTypeDefs_data(); + void testTemplateTypeDefs(); + void testTemplateTypeAliases(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testtoposort.cpp b/sources/shiboken6/ApiExtractor/tests/testtoposort.cpp new file mode 100644 index 000000000..50cefcfe9 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testtoposort.cpp @@ -0,0 +1,61 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testtoposort.h" +#include "graph.h" + +#include <QtTest/QTest> +#include <QtCore/QDebug> + +using IntGraph = Graph<int>; + +Q_DECLARE_METATYPE(IntGraph) + +using IntList = QList<int>; + +void TestTopoSort::testTopoSort_data() +{ + QTest::addColumn<IntGraph>("graph"); + QTest::addColumn<bool>("expectedValid"); + QTest::addColumn<IntList>("expectedOrder"); + + const int nodes1[] = {0, 1, 2}; + IntGraph g(std::begin(nodes1), std::end(nodes1)); + g.addEdge(1, 2); + g.addEdge(0, 1); + IntList expected = {0, 1, 2}; + QTest::newRow("DAG") << g << true << expected; + + const int nodes2[] = {0, 1}; + g.clear(); + g.setNodes(std::begin(nodes2), std::end(nodes2)); + expected = {1, 0}; + QTest::newRow("No edges") << g << true << expected; + + g.clear(); + g.setNodes(std::begin(nodes1), std::end(nodes1)); + g.addEdge(0, 1); + g.addEdge(1, 2); + g.addEdge(2, 0); + expected.clear(); + QTest::newRow("Cyclic") << g << false << expected; +} + +void TestTopoSort::testTopoSort() +{ + QFETCH(IntGraph, graph); + QFETCH(bool, expectedValid); + QFETCH(IntList, expectedOrder); + + const auto result = graph.topologicalSort(); + QCOMPARE(result.isValid(), expectedValid); + if (expectedValid) { + QCOMPARE(result.result, expectedOrder); + QVERIFY(result.cyclic.isEmpty()); + } else { + QVERIFY(!result.cyclic.isEmpty()); + } +} + +QTEST_APPLESS_MAIN(TestTopoSort) + diff --git a/sources/shiboken6/ApiExtractor/tests/testtoposort.h b/sources/shiboken6/ApiExtractor/tests/testtoposort.h new file mode 100644 index 000000000..4271d6a0e --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testtoposort.h @@ -0,0 +1,17 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTTOPOSORT_H +#define TESTTOPOSORT_H + +#include <QtCore/QObject> + +class TestTopoSort : public QObject +{ +Q_OBJECT +private slots: + void testTopoSort_data(); + void testTopoSort(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp b/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp new file mode 100644 index 000000000..72dae8cc5 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testtyperevision.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testtyperevision.h" +#include "testutil.h" +#include <abstractmetaenum.h> +#include <abstractmetalang.h> +#include <complextypeentry.h> +#include <enumtypeentry.h> +#include <flagstypeentry.h> +#include <typedatabase.h> + +#include <qtcompat.h> + +#include <QtTest/QTest> + +using namespace Qt::StringLiterals; + +void TestTypeRevision::testRevisionAttr() +{ + const char cppCode[] = "class Rev_0 {};" + "class Rev_1 {};" + "class Rev_2 { public: enum Rev_3 { X }; enum Rev_5 { Y }; };"; + const char xmlCode[] = "<typesystem package=\"Foo\">" + "<value-type name=\"Rev_0\"/>" + "<value-type name=\"Rev_1\" revision=\"1\"/>" + "<object-type name=\"Rev_2\" revision=\"2\">" + " <enum-type name=\"Rev_3\" revision=\"3\" flags=\"Flag_4\" flags-revision=\"4\" />" + " <enum-type name=\"Rev_5\" revision=\"5\" flags=\"Flag_5\" />" + "</object-type>" + "</typesystem>"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto rev0 = AbstractMetaClass::findClass(classes, "Rev_0"); + QCOMPARE(rev0->typeEntry()->revision(), 0); + + const auto rev1 = AbstractMetaClass::findClass(classes, "Rev_1"); + QCOMPARE(rev1->typeEntry()->revision(), 1); + + const auto rev2 = AbstractMetaClass::findClass(classes, "Rev_2"); + QCOMPARE(rev2->typeEntry()->revision(), 2); + + auto rev3 = rev2->findEnum(u"Rev_3"_s); + QVERIFY(rev3.has_value()); + QCOMPARE(rev3->typeEntry()->revision(), 3); + auto rev4 = rev3->typeEntry()->flags(); + QCOMPARE(rev4->revision(), 4); + auto rev5 = rev2->findEnum(u"Rev_5"_s); + QVERIFY(rev5.has_value()); + EnumTypeEntryCPtr revEnumTypeEntry = rev5->typeEntry(); + QCOMPARE(revEnumTypeEntry->revision(), 5); + QCOMPARE(revEnumTypeEntry->flags()->revision(), 5); +} + + +void TestTypeRevision::testVersion_data() +{ + QTest::addColumn<QString>("version"); + QTest::addColumn<int>("expectedClassCount"); + + QTest::newRow("none") << QString() << 2; + QTest::newRow("1.0") << QString::fromLatin1("1.0") << 1; // Bar20 excluded + QTest::newRow("2.0") << QString::fromLatin1("2.0") << 2; + QTest::newRow("3.0") << QString::fromLatin1("3.0") << 1; // Bar excluded by "until" +} + +void TestTypeRevision::testVersion() +{ + QFETCH(QString, version); + QFETCH(int, expectedClassCount); + + const char cppCode[] = R"CPP( +class Bar {}; +class Bar20 {}; +)CPP"; + const char xmlCode[] = R"XML( +<typesystem package="Foo"> + <value-type name="Bar" until="2.0"/> + <value-type name="Bar20" since="2.0"/> +</typesystem> +)XML"; + + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, true, version)); + QVERIFY(builder); + + QCOMPARE(builder->classes().size(), expectedClassCount); +} + +QTEST_APPLESS_MAIN(TestTypeRevision) + + diff --git a/sources/shiboken6/ApiExtractor/tests/testtyperevision.h b/sources/shiboken6/ApiExtractor/tests/testtyperevision.h new file mode 100644 index 000000000..84af839d2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testtyperevision.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTTYPEREVISION_H +#define TESTTYPEREVISION_H + +#include <QtCore/QObject> + +class TestTypeRevision : public QObject +{ + Q_OBJECT + +private slots: + void testRevisionAttr(); + void testVersion_data(); + void testVersion(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testutil.h b/sources/shiboken6/ApiExtractor/tests/testutil.h new file mode 100644 index 000000000..dc4e3b2da --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testutil.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTUTIL_H +#define TESTUTIL_H +#include <QtCore/QBuffer> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QTemporaryFile> +#include "abstractmetabuilder.h" +#include "reporthandler.h" +#include "typedatabase.h" + +#include <exception> +#include <memory> + +namespace TestUtil +{ + static AbstractMetaBuilder *parse(const char *cppCode, const char *xmlCode, + bool silent = true, + const QString &apiVersion = {}, + const QStringList &dropTypeEntries = {}, + LanguageLevel languageLevel = LanguageLevel::Default) + { + ReportHandler::setSilent(silent); + ReportHandler::startTimer(); + auto *td = TypeDatabase::instance(true); + if (apiVersion.isEmpty()) + TypeDatabase::clearApiVersions(); + else if (!TypeDatabase::setApiVersion(QLatin1StringView("*"), apiVersion)) + return nullptr; + td->setDropTypeEntries(dropTypeEntries); + QBuffer buffer; + // parse typesystem + buffer.setData(xmlCode); + if (!buffer.open(QIODevice::ReadOnly)) + return nullptr; + if (!td->parseFile(&buffer)) + return nullptr; + buffer.close(); + // parse C++ code + QTemporaryFile tempSource(QDir::tempPath() + QLatin1StringView("/st_XXXXXX_main.cpp")); + if (!tempSource.open()) { + qWarning().noquote().nospace() << "Creation of temporary file failed: " + << tempSource.errorString(); + return nullptr; + } + QByteArrayList arguments; + arguments.append(QFile::encodeName(tempSource.fileName())); + tempSource.write(cppCode, qint64(strlen(cppCode))); + tempSource.close(); + + auto builder = std::make_unique<AbstractMetaBuilder>(); + try { + if (!builder->build(arguments, {}, true, languageLevel)) + return nullptr; + } catch (const std::exception &e) { + qWarning("%s", e.what()); + return nullptr; + } + return builder.release(); + } +} // namespace TestUtil + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp b/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp new file mode 100644 index 000000000..98e30eac2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.cpp @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testvaluetypedefaultctortag.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetalang.h> +#include <complextypeentry.h> + +void TestValueTypeDefaultCtorTag::testValueTypeDefaultCtorTagArgument() +{ + const char cppCode[] = "\n\ + struct A {\n\ + A(int,int);\n\ + };\n\ + struct B {};\n\ + "; + const char xmlCode[] = "\n\ + <typesystem package='Foo'>\n\ + <primitive-type name='int' />\n\ + <value-type name='A' default-constructor='A(0, 0)' />\n\ + <value-type name='B' />\n\ + </typesystem>"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode, false)); + QVERIFY(builder); + + AbstractMetaClassList classes = builder->classes(); + + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + QVERIFY(classA->typeEntry()->hasDefaultConstructor()); + QCOMPARE(classA->typeEntry()->defaultConstructor(), u"A(0, 0)"); + + const auto classB = AbstractMetaClass::findClass(classes, "B"); + QVERIFY(classB); + QVERIFY(!classB->typeEntry()->hasDefaultConstructor()); +} + +QTEST_APPLESS_MAIN(TestValueTypeDefaultCtorTag) diff --git a/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.h b/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.h new file mode 100644 index 000000000..192c07c1d --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testvaluetypedefaultctortag.h @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTVALUETYPEDEFAULTCTORTAG_H +#define TESTVALUETYPEDEFAULTCTORTAG_H + +#include <QtCore/QObject> + +class TestValueTypeDefaultCtorTag : public QObject +{ + Q_OBJECT + private slots: + void testValueTypeDefaultCtorTagArgument(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp b/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp new file mode 100644 index 000000000..a600181a5 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testvoidarg.cpp @@ -0,0 +1,67 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "testvoidarg.h" +#include <QtTest/QTest> +#include "testutil.h" +#include <abstractmetaargument.h> +#include <abstractmetafunction.h> +#include <abstractmetalang.h> +#include <typesystem.h> + +void TestVoidArg::testVoidParsedFunction() +{ + const char cppCode[] = "struct A { void a(void); };"; + const char xmlCode[] = "\n\ + <typesystem package=\"Foo\">\n\ + <value-type name='A'/>\n\ + </typesystem>"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction("a"); + QVERIFY(addedFunc); + QCOMPARE(addedFunc->arguments().size(), 0); +} + +void TestVoidArg::testVoidAddedFunction() +{ + const char cppCode[] = "struct A { };"; + const char xmlCode[] = "\n\ + <typesystem package=\"Foo\">\n\ + <value-type name='A' >\n\ + <add-function signature=\"a(void)\"/>\n\ + </value-type>\n\ + </typesystem>"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction("a"); + QVERIFY(addedFunc); + QCOMPARE(addedFunc->arguments().size(), 0); + +} + +void TestVoidArg::testVoidPointerParsedFunction() +{ + const char cppCode[] = "struct A { void a(void*); };"; + const char xmlCode[] = "\n\ + <typesystem package=\"Foo\">\n\ + <value-type name='A' />\n\ + </typesystem>"; + QScopedPointer<AbstractMetaBuilder> builder(TestUtil::parse(cppCode, xmlCode)); + QVERIFY(builder); + AbstractMetaClassList classes = builder->classes(); + const auto classA = AbstractMetaClass::findClass(classes, "A"); + QVERIFY(classA); + const auto addedFunc = classA->findFunction("a"); + QVERIFY(addedFunc); + QCOMPARE(addedFunc->arguments().size(), 1); + +} + +QTEST_APPLESS_MAIN(TestVoidArg) diff --git a/sources/shiboken6/ApiExtractor/tests/testvoidarg.h b/sources/shiboken6/ApiExtractor/tests/testvoidarg.h new file mode 100644 index 000000000..191b9cfb2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/testvoidarg.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TESTVOIDARG_H +#define TESTVOIDARG_H +#include <QtCore/QObject> + +class TestVoidArg : public QObject +{ + Q_OBJECT +private slots: + void testVoidParsedFunction(); + void testVoidPointerParsedFunction(); + void testVoidAddedFunction(); +}; + +#endif diff --git a/sources/shiboken6/ApiExtractor/tests/utf8code.txt b/sources/shiboken6/ApiExtractor/tests/utf8code.txt new file mode 100644 index 000000000..6d5fa9dcf --- /dev/null +++ b/sources/shiboken6/ApiExtractor/tests/utf8code.txt @@ -0,0 +1 @@ +áéÃóú
\ No newline at end of file diff --git a/sources/shiboken6/ApiExtractor/textstream.cpp b/sources/shiboken6/ApiExtractor/textstream.cpp new file mode 100644 index 000000000..83d981b2b --- /dev/null +++ b/sources/shiboken6/ApiExtractor/textstream.cpp @@ -0,0 +1,263 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "textstream.h" + +#include <cstring> + +TextStream::TextStream(QIODevice *device, Language l) : + m_str(device), m_language(l) +{ +} + +TextStream::TextStream(QString *string, Language l) : + m_str(string), m_language(l) +{ +} + +TextStream::TextStream(QByteArray *array, Language l) : + m_str(array), m_language(l) +{ +} + +TextStream::~TextStream() = default; + +QChar TextStream::lastChar() const +{ + auto s = m_str.string(); + return s != nullptr && !s->isEmpty() ? *(s->crbegin()) : QChar(); +} + +void TextStream::setIndentation(int i) +{ + Q_ASSERT(i >= 0); + m_indentation = i; +} + +void TextStream::outdent(int n) +{ + m_indentation -= n; + Q_ASSERT(m_indentation >= 0); +} + +qint64 TextStream::pos() const +{ + // QTextStream::pos() only works for QIODevice, be a bit smarter + if (auto s = m_str.string()) + return s->size(); + // QIODevices need to flushed to tell the correct position. + const_cast<QTextStream &>(m_str).flush(); + return m_str.pos(); +} + +void TextStream::setString(QString *string, QIODeviceBase::OpenMode openMode) +{ + m_str.setString(string, openMode); + m_rstFormattingEnd = false; +} + +void TextStream::putRepetitiveChars(char c, int count) +{ + if (count > 0) { + for (int i = 0; i < count; ++i) { + const int ofw = m_str.fieldWidth(); + m_str.setFieldWidth(0); + m_str << c; + m_str.setFieldWidth(ofw); + } + } +} + +void TextStream::_setRstFormattingEnd() +{ + m_rstFormattingEnd = true; +} + +void TextStream::setLastCharClass(CharClass c) +{ + m_lastCharClass = c; +} + +void TextStream::writeIndent() +{ + putRepetitiveChars(' ', m_indentation * m_tabWidth); +} + +// Indent handling: If the last character was a new line +// and the upcoming one is none, indent the stream +// Special case for C++ : If the upcoming char is a '#', we don't +// indent (preprocessor directive). + +template <class Char> +static TextStream::CharClass charClassHelper(Char c) +{ + switch (c) { + case '\n': + return TextStream::CharClass::NewLine; + case '#': + return TextStream::CharClass::Hash; + case ' ': + case '\t': + return TextStream::CharClass::Space; + case '\\': + return TextStream::CharClass::BackSlash; + default: + break; + } + return TextStream::CharClass::Other; +} + +static inline TextStream::CharClass charClass(char c) +{ return charClassHelper(c); } + +static inline TextStream::CharClass charClass(QChar c) +{ return charClassHelper(c.unicode()); } + +void TextStream::checkIndent(CharClass upComingCharClass) +{ + if (m_rstFormattingEnd) { + if (upComingCharClass != CharClass::Space && upComingCharClass != CharClass::NewLine + && upComingCharClass != CharClass::BackSlash) { + m_str << '\\'; + } + m_rstFormattingEnd = false; + } + if (m_indentationEnabled && m_lastCharClass == CharClass::NewLine + && (upComingCharClass != CharClass::NewLine + && (m_language != Language::Cpp || upComingCharClass != CharClass::Hash))) { + writeIndent(); + } + m_lastCharClass = upComingCharClass; +} + +template <class Char> +void TextStream::putCharHelper(Char c) +{ + const auto klass = charClass(c); + checkIndent(klass); + m_str << c; +} + +void TextStream::putString(QStringView v) +{ + if (v.isEmpty()) + return; + if (v.contains(u'\n')) { + for (auto c : v) + putCharHelper(c); + } else { + // If there is no newline, write as a blob. This is important to make + // field formatting (alignment/width) working, else each char will be + // considered a field. + const auto klass = charClass(*v.cbegin()); + checkIndent(klass); + m_str << v; + m_lastCharClass = CharClass::Other; + } +} + +void TextStream::putChar(QChar c) +{ + putCharHelper(c); +} + +void TextStream::putString(const char *s) +{ + const char firstChar = *s; + if (firstChar == '\0') + return; + if (std::strchr(s, '\n') != nullptr) { // See above + for ( ; *s; ++s) + putCharHelper(*s); + } else { + checkIndent(charClass(firstChar)); + m_str << s; + m_lastCharClass = CharClass::Other; + } +} + +void TextStream::putChar(char c) +{ + putCharHelper(c); +} + +void TextStream::putInt(int t) +{ + checkIndent(CharClass::Other); + m_str << t; +} + +void TextStream::putSizeType(qsizetype t) +{ + checkIndent(CharClass::Other); + m_str << t; +} + +StringStream::StringStream(Language l) : TextStream(&m_buffer, l) +{ +} + +void StringStream::clear() +{ + m_buffer.clear(); + setLastCharClass(CharClass::NewLine); +} + +void indent(TextStream &s) +{ + s.indent(); +} + +void outdent(TextStream &s) +{ + s.outdent(); +} + +void enableIndent(TextStream &s) +{ + s.setIndentationEnabled(true); +} + +void disableIndent(TextStream &s) +{ + s.setIndentationEnabled(false); +} + +void ensureEndl(TextStream &s) +{ + if (s.lastChar() != u'\n') + s << '\n'; +} + +void rstBold(TextStream &s) +{ + s.putRawString("**"); +} + +void rstBoldOff(TextStream &s) +{ + s.putRawString("**"); + s._setRstFormattingEnd(); +} + +void rstItalic(TextStream &s) +{ + s.putRawChar('*'); +} + +void rstItalicOff(TextStream &s) +{ + s.putRawChar('*'); + s._setRstFormattingEnd(); +} + +void rstCode(TextStream &s) +{ + s.putRawString("``"); +} + +void rstCodeOff(TextStream &s) +{ + s.putRawString("``"); + s._setRstFormattingEnd(); +} diff --git a/sources/shiboken6/ApiExtractor/textstream.h b/sources/shiboken6/ApiExtractor/textstream.h new file mode 100644 index 000000000..228f36405 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/textstream.h @@ -0,0 +1,227 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TEXTSTREAM_H +#define TEXTSTREAM_H + +#include <QtCore/QTextStream> +#include <QtCore/QString> + +/// A text stream based on QTextStream with built-in indent. +class TextStream +{ +public: + Q_DISABLE_COPY_MOVE(TextStream) + + using ManipulatorFunc = void(TextStream &); + + enum class Language + { + None, Cpp + }; + + enum class CharClass + { + Other, NewLine, Space, Hash, BackSlash + }; + + explicit TextStream(QIODevice *device, Language l = Language::None); + explicit TextStream(QString *string, Language l = Language::None); + explicit TextStream(QByteArray *array, Language l = Language::None); + virtual ~TextStream(); + + Language language() const { return m_language; } + void setLanguage(Language language) { m_language = language; } + + bool isIndentationEnabled() const { return m_indentationEnabled; } + void setIndentationEnabled(bool m) + { m_indentationEnabled = m; } + + int tabWidth() const { return m_tabWidth; } + void setTabWidth(int tabWidth) { m_tabWidth = tabWidth; } + + void setFieldWidth(int f) { m_str.setFieldWidth(f); } + int fieldWidth() const { return m_str.fieldWidth(); } + + int indentation() const { return m_indentation; } + void setIndentation(int i); + + void indent(int n = 1) { m_indentation += n; } + void outdent(int n = 1); + + // QTextStream API + qint64 pos() const; + QTextStream::FieldAlignment fieldAlignment() const + { return m_str.fieldAlignment(); } + void setFieldAlignment(QTextStream::FieldAlignment al) + { m_str.setFieldAlignment(al); } + void setString(QString *string, QIODeviceBase::OpenMode openMode = QIODeviceBase::ReadWrite); + QString *string() const { return m_str.string(); } + void flush() { m_str.flush(); } + void setDevice(QIODevice *device) { m_str.setDevice(device); } + QIODevice *device() const { return m_str.device(); } + QTextStream &textStream() { return m_str; } + + // Last character written, works only for streams on strings + QChar lastChar() const; + + void putString(QStringView v); + void putChar(QChar c); + void putString(const char *s); + void putChar(char c); + + void putInt(int t); + void putSizeType(qsizetype t); + + void putRawString(const char *s) { m_str << s; } + void putRawChar(char c) { m_str << c; } + + TextStream &operator<<(QStringView v) { putString(v); return *this; } + TextStream &operator<<(const QString &qs) { putString(QStringView{qs}); return *this; } + TextStream &operator<<(QLatin1StringView lv) { putString(lv.constData()); return *this; } + TextStream &operator<<(QUtf8StringView uv) { putString(uv.data()); return *this; } + TextStream &operator<<(const QByteArray &ba) { putString(ba.constData()); return *this; } + TextStream &operator<<(QChar c) { putChar(c); return *this; } + TextStream &operator<<(const char *s) { putString(s); return *this; } + TextStream &operator<<(char c) { putChar(c); return *this; } + TextStream &operator<<(int t) { putInt(t); return *this; } +#if QT_POINTER_SIZE != 4 + TextStream &operator<<(qsizetype t) { putSizeType(t); return *this; } +#endif + + inline TextStream &operator<<(const QTextStreamManipulator &m) { m_str << m; return *this; } + inline TextStream &operator<<(ManipulatorFunc f) { f(*this); return *this; } + + void putRepetitiveChars(char c, int count); + + void _setRstFormattingEnd(); + +protected: + void setLastCharClass(CharClass c); + +private: + void writeIndent(); + void checkIndent(CharClass upComingCharClass); + template <class Char> + void putCharHelper(Char c); + + QTextStream m_str; + CharClass m_lastCharClass = CharClass::NewLine; + int m_tabWidth = 4; + int m_indentation = 0; + bool m_indentationEnabled = true; + bool m_rstFormattingEnd = false; // just past some **bla** where '\' needs to be enforced + Language m_language; +}; + +/// Stream into a string (cf std::ostringstream) +class StringStream : public TextStream +{ +public: + StringStream(Language l = Language::None); + + qsizetype size() const { return m_buffer.size(); } + void clear(); + + const QString &toString() const { return m_buffer; } + operator const QString &() const { return m_buffer; } + +private: + QString m_buffer; +}; + +void indent(TextStream &s); +void outdent(TextStream &s); +void enableIndent(TextStream &s); +void disableIndent(TextStream &s); +// Works only for streams on strings +void ensureEndl(TextStream &s); + +void rstBold(TextStream &s); +void rstBoldOff(TextStream &s); +void rstCode(TextStream &s); +void rstCodeOff(TextStream &s); +void rstItalic(TextStream &s); +void rstItalicOff(TextStream &s); + +inline TextStream &operator<<(TextStream &str, QAnyStringView asv) +{ + asv.visit([&str](auto s) { str << s; }); + return str; +} + +/// Format an aligned field +template <class T> +class AlignedField +{ +public: + explicit AlignedField(T value, int fieldWidth, + QTextStream::FieldAlignment a = QTextStream::AlignLeft) : + m_value(value), m_fieldWidth(fieldWidth), m_alignment(a) + { + } + + void put(TextStream &s) const + { + const int oldFieldWidth = s.fieldWidth(); + const auto oldFieldAlignment = s.fieldAlignment(); + s.setFieldWidth(m_fieldWidth); + s.setFieldAlignment(m_alignment); + const auto oldPos = s.pos(); + s << m_value; + // Ensure something is written when an empty string is encountered + if (oldPos == s.pos() && m_fieldWidth > 0) + s << ' '; + s.setFieldAlignment(oldFieldAlignment); + s.setFieldWidth(oldFieldWidth); + } + +private: + const T m_value; + const int m_fieldWidth; + const QTextStream::FieldAlignment m_alignment; +}; + +template <class T> +TextStream &operator<<(TextStream &str, const AlignedField<T> &fa) +{ + fa.put(str); + return str; +} + +class Pad +{ +public: + explicit Pad(char c, int count) : m_char(c), m_count(count) {} + + void write(TextStream &str) const + { + for (int i = 0; i < m_count; ++i) + str << m_char; + } + +private: + const char m_char; + const int m_count; +}; + +inline TextStream &operator<<(TextStream &str, const Pad &pad) +{ + pad.write(str); + return str; +} + +class Indentation +{ +public: + Q_DISABLE_COPY_MOVE(Indentation) + + Indentation(TextStream &s, int n = 1) : m_s(s), m_n(n) { m_s.indent(m_n); } + ~Indentation() { m_s.outdent(m_n); } + +private: + TextStream &m_s; + const int m_n; +}; + +#endif // TEXTSTREAM_H diff --git a/sources/shiboken6/ApiExtractor/typedatabase.cpp b/sources/shiboken6/ApiExtractor/typedatabase.cpp new file mode 100644 index 000000000..61fd22418 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typedatabase.cpp @@ -0,0 +1,1661 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "typedatabase.h" +#include "abstractmetatype.h" +#include "addedfunction.h" +#include "messages.h" +#include "typesystemparser_p.h" +#include "complextypeentry.h" +#include "constantvaluetypeentry.h" +#include "containertypeentry.h" +#include "customtypenentry.h" +#include "debughelpers_p.h" +#include "exception.h" +#include "flagstypeentry.h" +#include "functiontypeentry.h" +#include "namespacetypeentry.h" +#include "objecttypeentry.h" +#include "primitivetypeentry.h" +#include "optionsparser.h" +#include "pythontypeentry.h" +#include "smartpointertypeentry.h" +#include "typedefentry.h" +#include "typesystemtypeentry.h" +#include "varargstypeentry.h" +#include "voidtypeentry.h" +#include "conditionalstreamreader.h" +#include "predefined_templates.h" +#include "clangparser/compilersupport.h" +#include "modifications.h" + +#include "qtcompat.h" + +#include <QtCore/QBuffer> +#include <QtCore/QFile> +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QList> +#include <QtCore/QRegularExpression> +#include <QtCore/QVersionNumber> +#include <QtCore/QXmlStreamReader> +#include "reporthandler.h" + +#include <algorithm> +#include <utility> + +using namespace Qt::StringLiterals; + +using TypeDatabaseParserContextPtr = std::shared_ptr<TypeDatabaseParserContext>; + +// package -> api-version + +static QString wildcardToRegExp(QString w) +{ + w.replace(u'?', u'.'); + w.replace(u'*', ".*"_L1); + return w; +} + +using ApiVersion = std::pair<QRegularExpression, QVersionNumber>; +using ApiVersions = QList<ApiVersion>; + +Q_GLOBAL_STATIC(ApiVersions, apiVersions) + +struct PythonType +{ + QString name; + QString checkFunction; + TypeSystem::CPythonType type; +}; + +using PythonTypes = QList<PythonType>; + +static const PythonTypes &builtinPythonTypes() +{ + static const PythonTypes result{ + // "Traditional" custom types + // numpy + {u"PyArrayObject"_s, u"PyArray_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyBuffer"_s, u"Shiboken::Buffer::checkType"_s, TypeSystem::CPythonType::Other}, + {u"PyByteArray"_s, u"PyByteArray_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyBytes"_s, u"PyBytes_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyCallable"_s, u"PyCallable_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyDate"_s, u"PyDate_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyDateTime"_s, u"PyDateTime_Check_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyDict"_s, u"PyDict_Check"_s, TypeSystem::CPythonType::Other}, + // Convenience macro in sbkconverter.h + {u"PyObject"_s, u"true"_s, TypeSystem::CPythonType::Other}, + // shiboken-specific + {u"PyPathLike"_s, u"Shiboken::String::checkPath"_s, TypeSystem::CPythonType::Other}, + {u"PySequence"_s, u"Shiboken::String::checkIterableArgument"_s, + TypeSystem::CPythonType::Other}, + {u"PyUnicode"_s, u"PyUnicode_Check"_s, TypeSystem::CPythonType::String}, + {u"PyTypeObject"_s, u"PyType_Check"_s, TypeSystem::CPythonType::Other}, + {u"str"_s, u"Shiboken::String::check"_s, TypeSystem::CPythonType::String}, + // Types used as target lang API types for primitive types + {u"PyBool"_s, u"PyBool_Check"_s, TypeSystem::CPythonType::Bool}, + {u"PyComplex"_s, u"PyComplex_Check"_s, TypeSystem::CPythonType::Other}, + {u"PyLong"_s, u"PyLong_Check"_s, TypeSystem::CPythonType::Integer}, + {u"PyFloat"_s, u"PyFloat_Check"_s, TypeSystem::CPythonType::Float}, + // Single character strings to match C++ char types + {u"SbkChar"_s, u"SbkChar_Check"_s, TypeSystem::CPythonType::String} + }; + return result; +} + +struct SuppressedWarning +{ + QRegularExpression pattern; + QString rawText; + bool generate; // Current type system + mutable bool matched = false; +}; + +QList<OptionDescription> TypeDatabase::options() +{ + return { + {u"api-version=<\"package mask\">,<\"version\">"_s, + u"Specify the supported api version used to generate the bindings"_s}, + {u"drop-type-entries=\"<TypeEntry0>[;TypeEntry1;...]\""_s, + u"Semicolon separated list of type system entries (classes, namespaces,\n" + "global functions and enums) to be dropped from generation."_s}, + {u"-T<path>"_s, {} }, + {u"typesystem-paths="_s + OptionsParser::pathSyntax(), + u"Paths used when searching for typesystems"_s}, + {u"force-process-system-include-paths="_s + OptionsParser::pathSyntax(), + u"Include paths that are considered as system headers by the C++ parser, but should still " + "be processed to extract types (e.g. Qt include paths in a yocto sysroot)"_s}, + {u"keywords=keyword1[,keyword2,...]"_s, + u"A comma-separated list of keywords for conditional typesystem parsing"_s}, + }; +} + +struct TypeDatabaseOptions +{ + QStringList m_dropTypeEntries; + QStringList m_forceProcessSystemIncludes; + QStringList m_typesystemKeywords; + QStringList m_typesystemPaths; + bool m_suppressWarnings = true; +}; + +class TypeDatabaseOptionsParser : public OptionsParser +{ +public: + explicit TypeDatabaseOptionsParser(TypeDatabaseOptions *o) : m_options(o) {} + + bool handleBoolOption(const QString &key, OptionSource source) override; + bool handleOption(const QString &key, const QString &value, OptionSource source) override; + +private: + TypeDatabaseOptions *m_options; +}; + +bool TypeDatabaseOptionsParser::handleBoolOption(const QString &key, OptionSource source) +{ + switch (source) { + case OptionSource::CommandLine: + case OptionSource::ProjectFile: + if (key == u"no-suppress-warnings") { + m_options->m_suppressWarnings = false; + return true; + } + break; + case OptionSource::CommandLineSingleDash: + if (key.startsWith(u'T')) { // "-T/path" ends up a bool option + m_options->m_typesystemPaths += key.sliced(1).split(QDir::listSeparator(), + Qt::SkipEmptyParts); + return true; + } + break; + } + return false; +} + +bool TypeDatabaseOptionsParser::handleOption(const QString &key, const QString &value, + OptionSource source) +{ + if (source == OptionSource::CommandLineSingleDash) + return false; + if (key == u"api-version") { + const auto fullVersions = QStringView{value}.split(u'|'); + for (const auto &fullVersion : fullVersions) { + const auto parts = fullVersion.split(u','); + const QString package = parts.size() == 1 + ? u"*"_s : parts.constFirst().toString(); + const QString version = parts.constLast().toString(); + if (!TypeDatabase::setApiVersion(package, version)) + throw Exception(msgInvalidVersion(package, version)); + } + return true; + } + + if (key == u"drop-type-entries") { + m_options->m_dropTypeEntries = value.split(u';'); + m_options->m_dropTypeEntries.sort(); + return true; + } + + if (key == u"keywords") { + m_options->m_typesystemKeywords = value.split(u','); + return true; + } + + if (key == u"typesystem-paths") { + m_options->m_typesystemPaths += value.split(QDir::listSeparator(), + Qt::SkipEmptyParts); + return true; + } + + if (key == u"force-process-system-include-paths") { + m_options->m_forceProcessSystemIncludes += value.split(QDir::listSeparator(), + Qt::SkipEmptyParts); + return true; + } + + if (source == OptionSource::ProjectFile) { + if (key == u"typesystem-path") { + m_options->m_typesystemPaths += value; + return true; + } + } + + return false; +} + +struct TypeDatabasePrivate : public TypeDatabaseOptions +{ + TypeSystemTypeEntryCPtr defaultTypeSystemType() const; + TypeEntryPtr findType(const QString &name) const; + TypeEntryCList findCppTypes(const QString &name) const; + bool addType(TypeEntryPtr e, QString *errorMessage = nullptr); + bool parseFile(QIODevice *device, TypeDatabase *db, bool generate = true); + static bool parseFile(const TypeDatabaseParserContextPtr &context, + QIODevice *device, bool generate = true); + bool parseFile(const TypeDatabaseParserContextPtr &context, + const QString &filename, const QString ¤tPath, bool generate); + bool prepareParsing(QFile &file, const QString &origFileName, + const QString ¤tPath = {}); + + QString modifiedTypesystemFilepath(const QString& tsFile, + const QString ¤tPath) const; + void addBuiltInType(const TypeEntryPtr &e); + PrimitiveTypeEntryPtr addBuiltInPrimitiveType(const QString &name, + const TypeSystemTypeEntryCPtr &root, + const QString &rootPackage, + const CustomTypeEntryPtr &targetLang); + void addBuiltInCppStringPrimitiveType(const QString &name, + const QString &viewName, + const TypeSystemTypeEntryCPtr &root, + const QString &rootPackage, + const CustomTypeEntryPtr &targetLang); + void addBuiltInPrimitiveTypes(); + void addBuiltInContainerTypes(const TypeDatabaseParserContextPtr &context); + bool addOpaqueContainers(const TypeDatabaseParserContextPtr &context); + TypeEntryMultiMapConstIteratorRange findTypeRange(const QString &name) const; + template <class Predicate> + TypeEntryCList findTypesHelper(const QString &name, Predicate pred) const; + template <class Type, class Predicate> + QList<std::shared_ptr<const Type> > findTypesByTypeHelper(Predicate pred) const; + TypeEntryPtr resolveTypeDefEntry(const TypedefEntryPtr &typedefEntry, QString *errorMessage); + template <class String> + bool isSuppressedWarningHelper(const String &s) const; + bool resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context); + void formatDebug(QDebug &d) const; + void formatBuiltinTypes(QDebug &d) const; + + TypeEntryMultiMap m_entries; // Contains duplicate entries (cf addInlineNamespaceLookups). + TypeEntryMap m_flagsEntries; + TypedefEntryMap m_typedefEntries; + TemplateEntryMap m_templates; + QList<SuppressedWarning> m_suppressedWarnings; + QList<TypeSystemTypeEntryCPtr > m_typeSystemEntries; // maintain order, default is first. + + AddedFunctionList m_globalUserFunctions; + FunctionModificationList m_functionMods; + + QStringList m_requiredTargetImports; + + QHash<QString, bool> m_parsedTypesystemFiles; + + QList<TypeRejection> m_rejections; +}; + +static const char ENV_TYPESYSTEMPATH[] = "TYPESYSTEMPATH"; + +TypeDatabase::TypeDatabase() : d(new TypeDatabasePrivate) +{ + // Environment TYPESYSTEMPATH + if (qEnvironmentVariableIsSet(ENV_TYPESYSTEMPATH)) { + d->m_typesystemPaths + += qEnvironmentVariable(ENV_TYPESYSTEMPATH).split(QDir::listSeparator(), + Qt::SkipEmptyParts); + } + + d->addBuiltInType(TypeEntryPtr(new VoidTypeEntry())); + d->addBuiltInType(TypeEntryPtr(new VarargsTypeEntry())); + for (const auto &pt : builtinPythonTypes()) + d->addBuiltInType(TypeEntryPtr(new PythonTypeEntry(pt.name, pt.checkFunction, pt.type))); + + for (const auto &p : predefinedTemplates()) + addTemplate(p.name, p.content); +} + +TypeDatabase::~TypeDatabase() +{ + delete d; +} + +std::shared_ptr<OptionsParser> TypeDatabase::createOptionsParser() +{ + return std::make_shared<TypeDatabaseOptionsParser>(d); +} + +TypeDatabase *TypeDatabase::instance(bool newInstance) +{ + static TypeDatabase *db = nullptr; + if (!db || newInstance) { + delete db; + db = new TypeDatabase; + } + return db; +} + +// A list of regex/replacements to fix int types like "ushort" to "unsigned short" +// unless present in TypeDatabase +struct IntTypeNormalizationEntry +{ + QRegularExpression regex; + QString replacement; +}; + +using IntTypeNormalizationEntries = QList<IntTypeNormalizationEntry>; + +static const IntTypeNormalizationEntries &intTypeNormalizationEntries() +{ + static IntTypeNormalizationEntries result; + static bool firstTime = true; + if (firstTime) { + firstTime = false; + for (const auto &intType : {"char"_L1, "short"_L1, "int"_L1, "long"_L1}) { + if (!TypeDatabase::instance()->findType(u'u' + intType)) { + IntTypeNormalizationEntry entry; + entry.replacement = "unsigned "_L1 + intType; + entry.regex.setPattern("\\bu"_L1 + intType + "\\b"_L1); + Q_ASSERT(entry.regex.isValid()); + result.append(entry); + } + } + } + return result; +} + +// Normalization helpers +enum CharCategory { Space, Identifier, Other }; + +static CharCategory charCategory(QChar c) +{ + if (c.isSpace()) + return Space; + if (c.isLetterOrNumber() || c == u'_') + return Identifier; + return Other; +} + +// Normalize a C++ function signature: +// Drop space except between identifiers ("unsigned int", "const int") +static QString normalizeCppFunctionSignature(const QString &signatureIn) +{ + const QString signature = signatureIn.simplified(); + QString result; + result.reserve(signature.size()); + + CharCategory lastNonSpaceCategory = Other; + bool pendingSpace = false; + for (QChar c : signature) { + if (c.isSpace()) { + pendingSpace = true; + } else { + const auto category = charCategory(c); + if (pendingSpace) { + if (lastNonSpaceCategory == Identifier && category == Identifier) + result.append(u' '); + pendingSpace = false; + } + lastNonSpaceCategory = category; + result.append(c); + } + } + return result; +} + +// Normalize a signature for <add-function> by removing spaces +QString TypeDatabase::normalizedAddedFunctionSignature(const QString &signature) +{ + return normalizeCppFunctionSignature(signature); +} + +// Normalize a signature for matching by <modify-function>/<function> +// by removing spaces and changing const-ref to value. +// FIXME: PYSIDE7: Check whether the above simple normalization can be used +// here as well. Note though that const-ref would then have to be spelled out +// in typeystem XML. +QString TypeDatabase::normalizedSignature(const QString &signature) +{ + // QMetaObject::normalizedSignature() changes const-ref to value and + // changes "unsigned int" to "uint" which is undone by the below code + QByteArray normalizedB = QMetaObject::normalizedSignature(signature.toUtf8().constData()); + QString normalized = QLatin1StringView(normalizedB); + + if (instance() && signature.contains(u"unsigned")) { + const IntTypeNormalizationEntries &entries = intTypeNormalizationEntries(); + for (const auto &entry : entries) + normalized.replace(entry.regex, entry.replacement); + } + + return normalized; +} + +QStringList TypeDatabase::requiredTargetImports() const +{ + return d->m_requiredTargetImports; +} + +void TypeDatabase::addRequiredTargetImport(const QString& moduleName) +{ + if (!d->m_requiredTargetImports.contains(moduleName)) + d->m_requiredTargetImports << moduleName; +} + +QStringList TypeDatabase::typesystemKeywords() const +{ + QStringList result = d->m_typesystemKeywords; + for (const auto &d : d->m_dropTypeEntries) + result.append("no_"_L1 + d); + + switch (clang::emulatedCompilerLanguageLevel()) { + case LanguageLevel::Cpp11: + result.append(u"c++11"_s); + break; + case LanguageLevel::Cpp14: + result.append(u"c++14"_s); + break; + case LanguageLevel::Cpp17: + result.append(u"c++17"_s); + break; + case LanguageLevel::Cpp20: + result.append(u"c++20"_s); + break; + default: + break; + } + return result; +} + +IncludeList TypeDatabase::extraIncludes(const QString& className) const +{ + auto typeEntry = findComplexType(className); + return typeEntry ? typeEntry->extraIncludes() : IncludeList(); +} + +const QStringList &TypeDatabase::forceProcessSystemIncludes() const +{ + return d->m_forceProcessSystemIncludes; +} + +void TypeDatabase::addForceProcessSystemInclude(const QString &name) +{ + d->m_forceProcessSystemIncludes.append(name); +} + +// Add a lookup for the short name excluding inline namespaces +// so that "std::shared_ptr" finds "std::__1::shared_ptr" as well. +// Note: This inserts duplicate TypeEntryPtr into m_entries. +void TypeDatabase::addInlineNamespaceLookups(const NamespaceTypeEntryCPtr &n) +{ + TypeEntryList additionalEntries; // Store before modifying the hash + for (const auto &entry : std::as_const(d->m_entries)) { + if (entry->isChildOf(n)) + additionalEntries.append(entry); + } + for (const auto &ae : std::as_const(additionalEntries)) + d->m_entries.insert(ae->shortName(), ae); +} + +ContainerTypeEntryPtr TypeDatabase::findContainerType(const QString &name) const +{ + QString template_name = name; + + const auto pos = name.indexOf(u'<'); + if (pos > 0) + template_name = name.left(pos); + + auto type_entry = findType(template_name); + if (type_entry && type_entry->isContainer()) + return std::static_pointer_cast<ContainerTypeEntry>(type_entry); + return {}; +} + +static bool inline useType(const TypeEntryCPtr &t) +{ + return !t->isPrimitive() + || std::static_pointer_cast<const PrimitiveTypeEntry>(t)->preferredTargetLangType(); +} + +FunctionTypeEntryPtr TypeDatabase::findFunctionType(const QString &name) const +{ + const auto entries = d->findTypeRange(name); + for (const TypeEntryPtr &entry : entries) { + if (entry->type() == TypeEntry::FunctionType && useType(entry)) + return std::static_pointer_cast<FunctionTypeEntry>(entry); + } + return {}; +} + +void TypeDatabase::addTypeSystemType(const TypeSystemTypeEntryCPtr &e) +{ + d->m_typeSystemEntries.append(e); +} + +TypeSystemTypeEntryCPtr TypeDatabase::findTypeSystemType(const QString &name) const +{ + for (auto entry : d->m_typeSystemEntries) { + if (entry->name() == name) + return entry; + } + return {}; +} + +TypeSystemTypeEntryCPtr TypeDatabase::defaultTypeSystemType() const +{ + return d->defaultTypeSystemType(); +} + +QString TypeDatabase::loadedTypeSystemNames() const +{ + QString result; + for (const auto &entry : d->m_typeSystemEntries) { + if (!result.isEmpty()) + result += u", "_s; + result += entry->name(); + } + return result; +} + +TypeSystemTypeEntryCPtr TypeDatabasePrivate::defaultTypeSystemType() const +{ + return m_typeSystemEntries.value(0, nullptr); +} + +QString TypeDatabase::defaultPackageName() const +{ + Q_ASSERT(!d->m_typeSystemEntries.isEmpty()); + return d->m_typeSystemEntries.constFirst()->name(); +} + +TypeEntryPtr TypeDatabase::findType(const QString& name) const +{ + return d->findType(name); +} + +TypeEntryPtr TypeDatabasePrivate::findType(const QString& name) const +{ + const auto entries = findTypeRange(name); + for (const auto &entry : entries) { + if (useType(entry)) + return entry; + } + return {}; +} + +template <class Predicate> +TypeEntryCList TypeDatabasePrivate::findTypesHelper(const QString &name, Predicate pred) const +{ + TypeEntryCList result; + const auto entries = findTypeRange(name); + for (const auto &entry : entries) { + if (pred(entry)) + result.append(entry); + } + return result; +} + +template<class Type, class Predicate> +QList<std::shared_ptr<const Type> > TypeDatabasePrivate::findTypesByTypeHelper(Predicate pred) const +{ + QList<std::shared_ptr<const Type> > result; + for (const auto &entry : m_entries) { + if (pred(entry)) + result.append(std::static_pointer_cast<const Type>(entry)); + } + return result; +} + +TypeEntryCList TypeDatabase::findTypes(const QString &name) const +{ + return d->findTypesHelper(name, useType); +} + +static bool useCppType(const TypeEntryCPtr &t) +{ + bool result = false; + switch (t->type()) { + case TypeEntry::PrimitiveType: + case TypeEntry::VoidType: + case TypeEntry::FlagsType: + case TypeEntry::EnumType: + case TypeEntry::TemplateArgumentType: + case TypeEntry::BasicValueType: + case TypeEntry::ContainerType: + case TypeEntry::ObjectType: + case TypeEntry::ArrayType: + case TypeEntry::CustomType: + case TypeEntry::SmartPointerType: + case TypeEntry::TypedefType: + result = useType(t); + break; + default: + break; + } + return result; +} + +TypeEntryCList TypeDatabase::findCppTypes(const QString &name) const +{ + return d->findCppTypes(name); +} + +TypeEntryCList TypeDatabasePrivate::findCppTypes(const QString &name) const +{ + return findTypesHelper(name, useCppType); +} + +const TypeEntryMultiMap &TypeDatabase::entries() const +{ + return d->m_entries; +} + +const TypedefEntryMap &TypeDatabase::typedefEntries() const +{ + return d->m_typedefEntries; +} + +TypeEntryMultiMapConstIteratorRange TypeDatabasePrivate::findTypeRange(const QString &name) const +{ + const auto range = m_entries.equal_range(name); + return {range.first, range.second}; +} + +PrimitiveTypeEntryCList TypeDatabase::primitiveTypes() const +{ + auto pred = [](const TypeEntryCPtr &t) { return t->isPrimitive(); }; + return d->findTypesByTypeHelper<PrimitiveTypeEntry>(pred); +} + +ContainerTypeEntryCList TypeDatabase::containerTypes() const +{ + auto pred = [](const TypeEntryCPtr &t) { return t->isContainer(); }; + return d->findTypesByTypeHelper<ContainerTypeEntry>(pred); +} + +SmartPointerTypeEntryList TypeDatabase::smartPointerTypes() const +{ + auto pred = [](const TypeEntryCPtr &t) { return t->isSmartPointer(); }; + return d->findTypesByTypeHelper<SmartPointerTypeEntry>(pred); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const TypeRejection &r) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "TypeRejection(type=" << r.matchType << ", class=" + << r.className.pattern() << ", pattern=" << r.pattern.pattern() << ')'; + return d; +} +#endif // !QT_NO_DEBUG_STREAM + +void TypeDatabase::addRejection(const TypeRejection &r) +{ + d->m_rejections << r; +} + +// Match class name only +bool TypeDatabase::isClassRejected(const QString& className, QString *reason) const +{ + for (const TypeRejection& r : d->m_rejections) { + if (r.matchType == TypeRejection::ExcludeClass && r.className.match(className).hasMatch()) { + r.matched = true; + if (reason) + *reason = msgRejectReason(r); + return true; + } + } + return false; +} + +// Match class name and function/enum/field +static bool findRejection(const QList<TypeRejection> &rejections, + TypeRejection::MatchType matchType, + const QString& className, const QString& name, + QString *reason = nullptr) +{ + Q_ASSERT(matchType != TypeRejection::ExcludeClass); + for (const TypeRejection& r : rejections) { + if (r.matchType == matchType && r.pattern.match(name).hasMatch() + && r.className.match(className).hasMatch()) { + r.matched = true; + if (reason) + *reason = msgRejectReason(r, name); + return true; + } + } + return false; +} + +bool TypeDatabase::isEnumRejected(const QString& className, const QString& enumName, QString *reason) const +{ + return findRejection(d->m_rejections, TypeRejection::Enum, className, enumName, reason); +} + +TypeEntryPtr TypeDatabasePrivate::resolveTypeDefEntry(const TypedefEntryPtr &typedefEntry, + QString *errorMessage) +{ + QString sourceName = typedefEntry->sourceType(); + const auto lessThanPos = sourceName.indexOf(u'<'); + if (lessThanPos != -1) + sourceName.truncate(lessThanPos); + ComplexTypeEntryPtr source; + for (const auto &e : findTypeRange(sourceName)) { + switch (e->type()) { + case TypeEntry::BasicValueType: + case TypeEntry::ContainerType: + case TypeEntry::ObjectType: + case TypeEntry::SmartPointerType: + source = std::dynamic_pointer_cast<ComplexTypeEntry>(e); + Q_ASSERT(source); + break; + default: + break; + } + } + if (!source) { + if (errorMessage) + *errorMessage = msgUnableToResolveTypedef(typedefEntry->sourceType(), sourceName); + return nullptr; + } + + m_typedefEntries.insert(typedefEntry->qualifiedCppName(), typedefEntry); + return TypeDatabase::initializeTypeDefEntry(typedefEntry, source); +} + +ComplexTypeEntryPtr + TypeDatabase::initializeTypeDefEntry(const TypedefEntryPtr &typedefEntry, + const ComplexTypeEntryCPtr &source) +{ + ComplexTypeEntryPtr result(static_cast<ComplexTypeEntry *>(source->clone())); + result->useAsTypedef(typedefEntry); + typedefEntry->setSource(source); + typedefEntry->setTarget(result); + return result; +} + +bool TypeDatabase::addType(const TypeEntryPtr &e, QString *errorMessage) +{ + return d->addType(e, errorMessage); +} + +bool TypeDatabasePrivate::addType(TypeEntryPtr e, QString *errorMessage) +{ + if (e->type() == TypeEntry::TypedefType) { + e = resolveTypeDefEntry(std::static_pointer_cast<TypedefEntry>(e), errorMessage); + if (Q_UNLIKELY(!e)) + return false; + } + m_entries.insert(e->qualifiedCppName(), e); + return true; +} + +// Add a dummy value entry for non-type template parameters +ConstantValueTypeEntryPtr + TypeDatabase::addConstantValueTypeEntry(const QString &value, + const TypeEntryCPtr &parent) +{ + auto result = std::make_shared<ConstantValueTypeEntry>(value, parent); + result->setCodeGeneration(TypeEntry::GenerateNothing); + addType(result); + return result; +} + +bool TypeDatabase::isFunctionRejected(const QString& className, const QString& functionName, + QString *reason) const +{ + return findRejection(d->m_rejections, TypeRejection::Function, className, functionName, reason); +} + +bool TypeDatabase::isFieldRejected(const QString& className, const QString& fieldName, + QString *reason) const +{ + return findRejection(d->m_rejections, TypeRejection::Field, className, fieldName, reason); +} + +bool TypeDatabase::isArgumentTypeRejected(const QString& className, const QString& typeName, + QString *reason) const +{ + return findRejection(d->m_rejections, TypeRejection::ArgumentType, className, typeName, reason); +} + +bool TypeDatabase::isReturnTypeRejected(const QString& className, const QString& typeName, + QString *reason) const +{ + return findRejection(d->m_rejections, TypeRejection::ReturnType, className, typeName, reason); +} + +FlagsTypeEntryPtr TypeDatabase::findFlagsType(const QString &name) const +{ + TypeEntryPtr fte = findType(name); + if (!fte) { + fte = d->m_flagsEntries.value(name); + if (!fte) { + //last hope, search for flag without scope inside of flags hash + const auto end = d->m_flagsEntries.cend(); + for (auto it = d->m_flagsEntries.cbegin(); it != end; ++it) { + if (it.key().endsWith(name)) { + fte = it.value(); + break; + } + } + } + } + return std::static_pointer_cast<FlagsTypeEntry>(fte); +} + +void TypeDatabase::addFlagsType(const FlagsTypeEntryPtr &fte) +{ + d->m_flagsEntries[fte->originalName()] = fte; +} + +TemplateEntryPtr TypeDatabase::findTemplate(const QString &name) const +{ + return d->m_templates[name]; +} + +void TypeDatabase::addTemplate(const TemplateEntryPtr &t) +{ + d->m_templates[t->name()] = t; +} + +void TypeDatabase::addTemplate(const QString &name, const QString &code) +{ + auto te = std::make_shared<TemplateEntry>(name); + te->addCode(code); + addTemplate(te); +} + +AddedFunctionList TypeDatabase::globalUserFunctions() const +{ + return d->m_globalUserFunctions; +} + +void TypeDatabase::addGlobalUserFunctions(const AddedFunctionList &functions) +{ + d->m_globalUserFunctions << functions; +} + +AddedFunctionList TypeDatabase::findGlobalUserFunctions(const QString& name) const +{ + AddedFunctionList addedFunctions; + for (const AddedFunctionPtr &func : d->m_globalUserFunctions) { + if (func->name() == name) + addedFunctions.append(func); + } + return addedFunctions; +} + +void TypeDatabase::addGlobalUserFunctionModifications(const FunctionModificationList &functionModifications) +{ + d->m_functionMods << functionModifications; +} + +QString TypeDatabase::globalNamespaceClassName(const TypeEntryCPtr & /*entry*/) +{ + return u"Global"_s; +} + +FunctionModificationList + TypeDatabase::globalFunctionModifications(const QStringList &signatures) const +{ + FunctionModificationList lst; + for (const auto &mod : d->m_functionMods) { + if (mod.matches(signatures)) + lst << mod; + } + + return lst; +} + +bool TypeDatabase::addSuppressedWarning(const QString &warning, bool generate, + QString *errorMessage) +{ + QString pattern; + if (warning.startsWith(u'^') && warning.endsWith(u'$')) { + pattern = warning; + } else { + // Legacy syntax: Use wildcards '*' (unless escaped by '\') + QList<qsizetype> asteriskPositions; + const auto warningSize = warning.size(); + for (qsizetype i = 0, warningSize = warning.size(); i < warningSize; ++i) { + if (warning.at(i) == u'\\') + ++i; + else if (warning.at(i) == u'*') + asteriskPositions.append(i); + } + asteriskPositions.append(warningSize); + + pattern.append(u'^'); + qsizetype lastPos = 0; + for (qsizetype a = 0, aSize = asteriskPositions.size(); a < aSize; ++a) { + if (a) + pattern.append(".*"_L1); + const auto nextPos = asteriskPositions.at(a); + if (nextPos > lastPos) + pattern.append(QRegularExpression::escape(warning.mid(lastPos, nextPos - lastPos))); + lastPos = nextPos + 1; + } + pattern.append(u'$'); + } + + QRegularExpression expression(pattern); + if (!expression.isValid()) { + *errorMessage = u"Invalid message pattern \""_s + warning + + u"\": "_s + expression.errorString(); + return false; + } + expression.setPatternOptions(expression.patternOptions() | QRegularExpression::MultilineOption); + + d->m_suppressedWarnings.append({expression, warning, generate}); + return true; +} + +bool TypeDatabase::isSuppressedWarning(QStringView s) const +{ + if (!d->m_suppressWarnings) + return false; + auto wit = std::find_if(d->m_suppressedWarnings.cbegin(), d->m_suppressedWarnings.cend(), + [&s] (const SuppressedWarning &e) { + return e.pattern.matchView(s).hasMatch(); + }); + const bool found = wit != d->m_suppressedWarnings.cend(); + if (found) + wit->matched = true; + return found; +} + +QString TypeDatabase::modifiedTypesystemFilepath(const QString& tsFile, const QString ¤tPath) const +{ + return d->modifiedTypesystemFilepath(tsFile, currentPath); +} + +void TypeDatabase::logUnmatched() const +{ + for (auto &sw : d->m_suppressedWarnings) { + if (sw.generate && !sw.matched) + qWarning("Unmatched suppressed warning: \"%s\"", qPrintable(sw.rawText)); + } + + for (auto &tr : d->m_rejections) { + if (tr.generate && !tr.matched) { + QDebug d = qWarning(); + d.noquote(); + d.nospace(); + d << "Unmatched rejection: " << tr.matchType; + if (!tr.className.pattern().isEmpty()) + d << " class " << tr.className.pattern(); + if (!tr.pattern.pattern().isEmpty()) + d << " \"" << tr.pattern.pattern() << '"'; + } + } +} + +QString TypeDatabasePrivate::modifiedTypesystemFilepath(const QString& tsFile, + const QString ¤tPath) const +{ + const QFileInfo tsFi(tsFile); + if (tsFi.isAbsolute()) // No point in further lookups + return tsFi.absoluteFilePath(); + if (tsFi.isFile()) // Make path absolute + return tsFi.absoluteFilePath(); + if (!currentPath.isEmpty()) { + const QFileInfo fi(currentPath + u'/' + tsFile); + if (fi.isFile()) + return fi.absoluteFilePath(); + } + for (const QString &path : m_typesystemPaths) { + const QFileInfo fi(path + u'/' + tsFile); + if (fi.isFile()) + return fi.absoluteFilePath(); + } + return tsFile; +} + +void TypeDatabasePrivate::addBuiltInContainerTypes(const TypeDatabaseParserContextPtr &context) +{ + // Unless the user has added the standard containers (potentially with + // some opaque types), add them by default. + const bool hasStdArray = findType(u"std::array"_s) != nullptr; + const bool hasStdPair = findType(u"std::pair"_s) != nullptr; + const bool hasStdList = findType(u"std::list"_s) != nullptr; + const bool hasStdVector = findType(u"std::vector"_s) != nullptr; + const bool hasStdMap = findType(u"std::map"_s) != nullptr; + const bool hasStdUnorderedMap = findType(u"std::unordered_map"_s) != nullptr; + const bool hasStdSpan = findType(u"std::span"_s) != nullptr; + + if (hasStdPair && hasStdList && hasStdVector && hasStdMap && hasStdUnorderedMap) + return; + + QByteArray ts = R"(<?xml version="1.0" encoding="UTF-8"?><typesystem>)"; + if (!hasStdArray) { + ts += containerTypeSystemSnippet( + "std::array", "list", "array", + "shiboken_conversion_cppsequence_to_pylist", + "PySequence", + "shiboken_conversion_pyiterable_to_cpparray"); + } + if (!hasStdPair) { + ts += containerTypeSystemSnippet( + "std::pair", "pair", "utility", + "shiboken_conversion_cpppair_to_pytuple", + "PySequence", "shiboken_conversion_pysequence_to_cpppair"); + } + if (!hasStdList) { + ts += containerTypeSystemSnippet( + "std::list", "list", "list", + "shiboken_conversion_cppsequence_to_pylist", + "PySequence", + "shiboken_conversion_pyiterable_to_cppsequentialcontainer"); + } + if (!hasStdVector) { + ts += containerTypeSystemSnippet( + "std::vector", "list", "vector", + "shiboken_conversion_cppsequence_to_pylist", + "PySequence", + "shiboken_conversion_pyiterable_to_cppsequentialcontainer_reserve"); + } + if (!hasStdMap) { + ts += containerTypeSystemSnippet( + "std::map", "map", "map", + "shiboken_conversion_stdmap_to_pydict", + "PyDict", "shiboken_conversion_pydict_to_stdmap"); + } + if (!hasStdUnorderedMap) { + ts += containerTypeSystemSnippet( + "std::unordered_map", "map", "unordered_map", + "shiboken_conversion_stdmap_to_pydict", + "PyDict", "shiboken_conversion_pydict_to_stdmap"); + } + if (!hasStdSpan + && clang::emulatedCompilerLanguageLevel() >= LanguageLevel::Cpp20) { + auto spanSnip = containerTypeSystemSnippet( + "std::span", "span", "span", + "shiboken_conversion_cppsequence_to_pylist"); + auto pos = spanSnip.indexOf('>'); + spanSnip.insert(pos, R"( view-on="std::vector")"); + ts += spanSnip; + } + + ts += "</typesystem>"; + QBuffer buffer(&ts); + buffer.open(QIODevice::ReadOnly); + const bool ok = parseFile(context, &buffer, true); + Q_ASSERT(ok); +} + +bool TypeDatabasePrivate::addOpaqueContainers(const TypeDatabaseParserContextPtr &context) +{ + const auto &och = context->opaqueContainerHash; + for (auto it = och.cbegin(), end = och.cend(); it != end; ++it) { + const QString &name = it.key(); + auto te = findType(name); + if (!te || !te->isContainer()) { + qCWarning(lcShiboken, "No container \"%s\" found.", qPrintable(name)); + return false; + } + auto cte = std::static_pointer_cast<ContainerTypeEntry>(te); + cte->appendOpaqueContainers(it.value()); + } + return true; +} + +bool TypeDatabase::parseFile(const QString &filename, bool generate) +{ + QString filepath = modifiedTypesystemFilepath(filename, {}); + QFile file(filepath); + return d->prepareParsing(file, filename) && d->parseFile(&file, this, generate); +} + +bool TypeDatabase::parseFile(const TypeDatabaseParserContextPtr &context, + const QString &filename, const QString ¤tPath, + bool generate) +{ + return d->parseFile(context, filename, currentPath, generate); +} + +bool TypeDatabasePrivate::prepareParsing(QFile &file, const QString &origFileName, + const QString ¤tPath) +{ + const QString &filepath = file.fileName(); + if (!file.exists()) { + m_parsedTypesystemFiles[filepath] = false; + QString message = u"Can't find "_s + origFileName; + if (!currentPath.isEmpty()) + message += u", current path: "_s + currentPath; + message += u", typesystem paths: "_s + m_typesystemPaths.join(u", "_s); + qCWarning(lcShiboken, "%s", qPrintable(message)); + return false; + } + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + m_parsedTypesystemFiles[filepath] = false; + qCWarning(lcShiboken, "%s", qPrintable(msgCannotOpenForReading(file))); + return false; + } + + m_parsedTypesystemFiles[filepath] = true; + return true; +} + +bool TypeDatabasePrivate::parseFile(const TypeDatabaseParserContextPtr &context, + const QString &filename, const QString ¤tPath, + bool generate) +{ + // Prevent recursion when including self. + QString filepath = modifiedTypesystemFilepath(filename, currentPath); + const auto it = m_parsedTypesystemFiles.constFind(filepath); + if (it != m_parsedTypesystemFiles.cend()) + return it.value(); + + QFile file(filepath); + if (!prepareParsing(file, filename, currentPath)) + return false; + + const bool ok = parseFile(context, &file, generate); + m_parsedTypesystemFiles[filepath] = ok; + return ok; +} + +bool TypeDatabase::parseFile(QIODevice *device, bool generate) +{ + return d->parseFile(device, this, generate); +} + +bool TypeDatabasePrivate::parseFile(QIODevice *device, TypeDatabase *db, bool generate) +{ + const auto context = std::make_shared<TypeDatabaseParserContext>(); + context->db = db; + + if (!parseFile(context, device, generate)) + return false; + + addBuiltInPrimitiveTypes(); + addBuiltInContainerTypes(context); + return addOpaqueContainers(context) + && resolveSmartPointerInstantiations(context); +} + +bool TypeDatabase::parseFile(const TypeDatabaseParserContextPtr &context, + QIODevice *device, bool generate) +{ + return d->parseFile(context, device, generate); +} + +bool TypeDatabasePrivate::parseFile(const TypeDatabaseParserContextPtr &context, + QIODevice *device, bool generate) +{ + ConditionalStreamReader reader(device); + reader.setConditions(context->db->typesystemKeywords()); + TypeSystemParser handler(context, generate); + const bool result = handler.parse(reader); + if (!result) { + qCWarning(lcShiboken, "%s", qPrintable(handler.errorString())); + return false; + } + return result; +} + +// Split a type list potentially with template types +// "A<B,C>,D" -> ("A<B,C>", "D") +static QStringList splitTypeList(const QString &s) +{ + QStringList result; + int templateDepth = 0; + qsizetype lastPos = 0; + const auto size = s.size(); + for (qsizetype i = 0; i < size; ++i) { + switch (s.at(i).toLatin1()) { + case '<': + ++templateDepth; + break; + case '>': + --templateDepth; + break; + case ',': + if (templateDepth == 0) { + result.append(s.mid(lastPos, i - lastPos).trimmed()); + lastPos = i + 1; + } + break; + } + } + if (lastPos < size) + result.append(s.mid(lastPos, size - lastPos).trimmed()); + return result; +} + +bool TypeDatabasePrivate::resolveSmartPointerInstantiations(const TypeDatabaseParserContextPtr &context) +{ + const auto &instantiations = context->smartPointerInstantiations; + for (auto it = instantiations.cbegin(), end = instantiations.cend(); it != end; ++it) { + auto smartPointerEntry = it.key(); + const auto instantiationNames = splitTypeList(it.value()); + SmartPointerTypeEntry::Instantiations instantiations; + instantiations.reserve(instantiationNames.size()); + for (const auto &instantiation : instantiationNames) { + QString name; + QString type = instantiation; + const auto equalsPos = instantiation.indexOf(u'='); + if (equalsPos != -1) { + type.truncate(equalsPos); + name = instantiation.mid(equalsPos + 1); + } + + const auto typeEntries = findCppTypes(type); + if (typeEntries.isEmpty()) { + const QString m = msgCannotFindTypeEntryForSmartPointer(type, + smartPointerEntry->name()); + qCWarning(lcShiboken, "%s", qPrintable(m)); + return false; + } + if (typeEntries.size() > 1) { + const QString m = msgAmbiguousTypesFound(type, typeEntries); + qCWarning(lcShiboken, "%s", qPrintable(m)); + return false; + } + instantiations.append({name, typeEntries.constFirst()}); + } + smartPointerEntry->setInstantiations(instantiations); + } + return true; +} + +PrimitiveTypeEntryPtr TypeDatabase::findPrimitiveType(const QString& name) const +{ + const auto entries = d->findTypeRange(name); + for (const auto &entry : entries) { + if (entry->isPrimitive()) { + auto pe = std::static_pointer_cast<PrimitiveTypeEntry>(entry); + if (pe->preferredTargetLangType()) + return pe; + } + } + + return nullptr; +} + +ComplexTypeEntryPtr TypeDatabase::findComplexType(const QString& name) const +{ + const auto entries = d->findTypeRange(name); + for (const auto &entry : entries) { + if (entry->isComplex() && useType(entry)) + return std::static_pointer_cast<ComplexTypeEntry>(entry); + } + return nullptr; +} + +ObjectTypeEntryPtr TypeDatabase::findObjectType(const QString& name) const +{ + const auto entries = d->findTypeRange(name); + for (const auto &entry : entries) { + if (entry && entry->isObject() && useType(entry)) + return std::static_pointer_cast<ObjectTypeEntry>(entry); + } + return nullptr; +} + +NamespaceTypeEntryList TypeDatabase::findNamespaceTypes(const QString& name) const +{ + NamespaceTypeEntryList result; + const auto entries = d->findTypeRange(name); + for (const auto &entry : entries) { + if (entry->isNamespace()) + result.append(std::static_pointer_cast<NamespaceTypeEntry>(entry)); + } + return result; +} + +NamespaceTypeEntryPtr TypeDatabase::findNamespaceType(const QString& name, + const QString &fileName) const +{ + const auto entries = findNamespaceTypes(name); + // Preferably check on matching file name first, if a pattern was given. + if (!fileName.isEmpty()) { + for (const auto &entry : entries) { + if (entry->hasPattern() && entry->matchesFile(fileName)) + return entry; + } + } + for (const auto &entry : entries) { + if (!entry->hasPattern()) + return entry; + } + return nullptr; +} + +bool TypeDatabase::shouldDropTypeEntry(const QString& fullTypeName) const +{ + return d->m_dropTypeEntries.contains(fullTypeName); +} + +void TypeDatabase::setDropTypeEntries(const QStringList &dropTypeEntries) +{ + d->m_dropTypeEntries = dropTypeEntries; + d->m_dropTypeEntries.sort(); +} + +static bool computeTypeIndexes = true; +static int maxTypeIndex; + +static bool typeEntryLessThan(const TypeEntryCPtr &t1, const TypeEntryCPtr &t2) +{ + if (t1->revision() < t2->revision()) + return true; + return t1->revision() == t2->revision() + && t1->qualifiedCppName() < t2->qualifiedCppName(); +} + +static void _computeTypeIndexes() +{ + auto *tdb = TypeDatabase::instance(); + + TypeEntryList list; + + // Group type entries by revision numbers + const auto &allEntries = tdb->entries(); + list.reserve(allEntries.size()); + for (auto tit = allEntries.cbegin(), end = allEntries.cend(); tit != end; ++tit) { + const TypeEntryPtr &entry = tit.value(); + if (entry->isPrimitive() + || entry->isContainer() + || entry->isFunction() + || !entry->generateCode() + || entry->isEnumValue() + || entry->isVarargs() + || entry->isTypeSystem() + || entry->isVoid() + || entry->isCustom()) + continue; + if (!list.contains(entry)) // Remove duplicates + list.append(entry); + } + + // Sort the type entries by revision, name + std::sort(list.begin(), list.end(), typeEntryLessThan); + + maxTypeIndex = 0; + for (const TypeEntryPtr &e : std::as_const(list)) + e->setSbkIndex(maxTypeIndex++); + computeTypeIndexes = false; +} + +void TypeEntry::setRevision(int r) +{ + if (setRevisionHelper(r)) + computeTypeIndexes = true; +} + +int TypeEntry::sbkIndex() const +{ + if (computeTypeIndexes) + _computeTypeIndexes(); + return sbkIndexHelper(); +} + +int getMaxTypeIndex() +{ + if (computeTypeIndexes) + _computeTypeIndexes(); + return maxTypeIndex; +} + +void TypeDatabase::clearApiVersions() +{ + apiVersions()->clear(); +} + +bool TypeDatabase::setApiVersion(const QString& packageWildcardPattern, const QString &version) +{ + const QString packagePattern = wildcardToRegExp(packageWildcardPattern.trimmed()); + const QVersionNumber versionNumber = QVersionNumber::fromString(version); + if (versionNumber.isNull()) + return false; + ApiVersions &versions = *apiVersions(); + for (qsizetype i = 0, size = versions.size(); i < size; ++i) { + if (versions.at(i).first.pattern() == packagePattern) { + versions[i].second = versionNumber; + return true; + } + } + const QRegularExpression packageRegex(packagePattern); + if (!packageRegex.isValid()) + return false; + versions.append(std::make_pair(packageRegex, versionNumber)); + return true; +} + +bool TypeDatabase::checkApiVersion(const QString &package, + const VersionRange &vr) +{ + const ApiVersions &versions = *apiVersions(); + if (versions.isEmpty()) // Nothing specified: use latest. + return true; + for (qsizetype i = 0, size = versions.size(); i < size; ++i) { + if (versions.at(i).first.match(package).hasMatch()) + return versions.at(i).second >= vr.since + && versions.at(i).second <= vr.until; + } + return false; +} + +bool TypeDatabase::hasDroppedTypeEntries() const +{ + return !d->m_dropTypeEntries.isEmpty(); +} + +#ifndef QT_NO_DEBUG_STREAM +void TypeDatabase::formatDebug(QDebug &debug) const +{ + d->formatDebug(debug); +} + +void TypeDatabasePrivate::formatDebug(QDebug &d) const +{ + d << "TypeDatabase(" + << "entries[" << m_entries.size() << "]="; + for (auto it = m_entries.cbegin(), end = m_entries.cend(); it != end; ++it) + d << " " << it.value() << '\n'; + if (!m_typedefEntries.isEmpty()) { + d << "typedefs[" << m_typedefEntries.size() << "]=("; + const auto begin = m_typedefEntries.cbegin(); + for (auto it = begin, end = m_typedefEntries.cend(); it != end; ++it) { + if (it != begin) + d << ", "; + d << " " << it.value() << '\n'; + } + d << ")\n"; + } + if (!m_templates.isEmpty()) { + d << "templates[" << m_templates.size() << "]=("; + const auto begin = m_templates.cbegin(); + for (auto it = begin, end = m_templates.cend(); it != end; ++it) { + if (it != begin) + d << ", "; + d << it.value(); + } + d << ")\n"; + } + if (!m_flagsEntries.isEmpty()) { + d << "flags[" << m_flagsEntries.size() << "]=("; + const auto begin = m_flagsEntries.cbegin(); + for (auto it = begin, end = m_flagsEntries.cend(); it != end; ++it) { + if (it != begin) + d << ", "; + d << it.value(); + } + d << ")\n"; + } + d <<"\nglobalUserFunctions=" << m_globalUserFunctions << '\n'; + formatList(d, "globalFunctionMods", m_functionMods, "\n"); + d << ')'; +} + +// Helpers for dumping out primitive type info + +struct formatPrimitiveEntry +{ + explicit formatPrimitiveEntry(const PrimitiveTypeEntryCPtr &e) : m_pe(e) {} + + PrimitiveTypeEntryCPtr m_pe; +}; + +QDebug operator<<(QDebug debug, const formatPrimitiveEntry &fe) +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + const QString &name = fe.m_pe->name(); + const QString &targetLangName = fe.m_pe->targetLangApiName(); + debug << '"' << name << '"'; + if (name != targetLangName) + debug << " (\"" << targetLangName << "\")"; + if (fe.m_pe->isBuiltIn()) + debug << " [builtin]"; + if (isExtendedCppPrimitive(fe.m_pe)) { + debug << " ["; + if (!isCppPrimitive(fe.m_pe)) + debug << "extended "; + debug << "C++]"; + } + return debug; +} + +// Sort primitive types for displaying; base type and typedef'ed types +struct PrimitiveFormatListEntry +{ + PrimitiveTypeEntryCPtr baseType; + PrimitiveTypeEntryCList typedefs; +}; + +static bool operator<(const PrimitiveFormatListEntry &e1, const PrimitiveFormatListEntry &e2) +{ + return e1.baseType->name() < e2.baseType->name(); +} + +using PrimitiveFormatListEntries = QList<PrimitiveFormatListEntry>; + +static qsizetype indexOf(const PrimitiveFormatListEntries &e, const PrimitiveTypeEntryCPtr &needle) +{ + for (qsizetype i = 0, size = e.size(); i < size; ++i) { + if (e.at(i).baseType == needle) + return i; + } + return -1; +} + +void TypeDatabase::formatBuiltinTypes(QDebug debug) const +{ + QDebugStateSaver saver(debug); + debug.noquote(); + debug.nospace(); + + // Determine base types and their typedef'ed types + QList<PrimitiveFormatListEntry> primitiveEntries; + for (const auto &e : std::as_const(d->m_entries)) { + if (e->isPrimitive()) { + auto pe = std::static_pointer_cast<const PrimitiveTypeEntry>(e); + auto basic = basicReferencedTypeEntry(pe); + if (basic != pe) { + const auto idx = indexOf(primitiveEntries, basic); + if (idx != -1) + primitiveEntries[idx].typedefs.append(pe); + else + primitiveEntries.append(PrimitiveFormatListEntry{basic, {pe}}); + } else { + primitiveEntries.append(PrimitiveFormatListEntry{pe, {}}); + } + } + } + + std::sort(primitiveEntries.begin(), primitiveEntries.end()); + + for (const auto &e : std::as_const(primitiveEntries)) { + debug << "Primitive: " << formatPrimitiveEntry(e.baseType) << '\n'; + for (const auto &pe : e.typedefs) + debug << " " << formatPrimitiveEntry(pe) << '\n'; + } +} + +void TypeDatabasePrivate::addBuiltInType(const TypeEntryPtr &e) +{ + e->setBuiltIn(true); + addType(e); +} + +PrimitiveTypeEntryPtr + TypeDatabasePrivate::addBuiltInPrimitiveType(const QString &name, + const TypeSystemTypeEntryCPtr &root, + const QString &rootPackage, + const CustomTypeEntryPtr &targetLang) +{ + auto result = std::make_shared<PrimitiveTypeEntry>(name, QVersionNumber{}, root); + result->setTargetLangApiType(targetLang); + result->setTargetLangPackage(rootPackage); + addBuiltInType(result); + return result; +} + +void TypeDatabasePrivate::addBuiltInCppStringPrimitiveType(const QString &name, + const QString &viewName, + const TypeSystemTypeEntryCPtr &root, + const QString &rootPackage, + const CustomTypeEntryPtr &targetLang) + +{ + auto stringType = addBuiltInPrimitiveType(name, root, rootPackage, + targetLang); + auto viewType = addBuiltInPrimitiveType(viewName, root, rootPackage, + nullptr); + viewType->setViewOn(stringType); +} + +void TypeDatabasePrivate::addBuiltInPrimitiveTypes() +{ + auto root = defaultTypeSystemType(); + const QString &rootPackage = root->name(); + + // C++ primitive types + auto pyLongEntry = findType(u"PyLong"_s); + Q_ASSERT(pyLongEntry && pyLongEntry->isCustom()); + auto pyLongCustomEntry = std::static_pointer_cast<CustomTypeEntry>(pyLongEntry); + auto pyBoolEntry = findType(u"PyBool"_s); + Q_ASSERT(pyBoolEntry && pyBoolEntry->isCustom()); + auto sbkCharEntry = findType(u"SbkChar"_s); + Q_ASSERT(sbkCharEntry && sbkCharEntry->isCustom()); + auto sbkCharCustomEntry = std::static_pointer_cast<CustomTypeEntry>(sbkCharEntry); + + auto pyBoolCustomEntry = std::static_pointer_cast<CustomTypeEntry>(pyBoolEntry); + for (const auto &t : AbstractMetaType::cppIntegralTypes()) { + if (!m_entries.contains(t)) { + CustomTypeEntryPtr targetLangApi = pyLongCustomEntry; + if (t == u"bool") + targetLangApi = pyBoolCustomEntry; + else if (AbstractMetaType::cppCharTypes().contains(t)) + targetLangApi = sbkCharCustomEntry; + addBuiltInPrimitiveType(t, root, rootPackage, targetLangApi); + } + } + + auto pyFloatEntry = findType(u"PyFloat"_s); + Q_ASSERT(pyFloatEntry && pyFloatEntry->isCustom()); + auto pyFloatCustomEntry = std::static_pointer_cast<CustomTypeEntry>(pyFloatEntry); + for (const auto &t : AbstractMetaType::cppFloatTypes()) { + if (!m_entries.contains(t)) + addBuiltInPrimitiveType(t, root, rootPackage, pyFloatCustomEntry); + } + + auto pyUnicodeEntry = findType(u"PyUnicode"_s); + Q_ASSERT(pyUnicodeEntry && pyUnicodeEntry->isCustom()); + auto pyUnicodeCustomEntry = std::static_pointer_cast<CustomTypeEntry>(pyUnicodeEntry); + + constexpr auto stdString = "std::string"_L1; + if (!m_entries.contains(stdString)) { + addBuiltInCppStringPrimitiveType(stdString, u"std::string_view"_s, + root, rootPackage, + pyUnicodeCustomEntry); + } + constexpr auto stdWString = "std::wstring"_L1; + if (!m_entries.contains(stdWString)) { + addBuiltInCppStringPrimitiveType(stdWString, u"std::wstring_view"_s, + root, rootPackage, + pyUnicodeCustomEntry); + } +} + +QDebug operator<<(QDebug d, const TypeDatabase &db) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + db.formatDebug(d); + return d; +} +#endif // !QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/typedatabase.h b/sources/shiboken6/ApiExtractor/typedatabase.h new file mode 100644 index 000000000..d5adca324 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typedatabase.h @@ -0,0 +1,208 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPEDATABASE_H +#define TYPEDATABASE_H + +#include "include.h" +#include "modifications_typedefs.h" +#include "typedatabase_typedefs.h" + +#include <QtCore/QRegularExpression> +#include <QtCore/QStringList> +#include <QtCore/QVersionNumber> + +#include <memory> + +QT_FORWARD_DECLARE_CLASS(QIODevice) + +struct OptionDescription; +class OptionsParser; +struct TypeDatabasePrivate; +struct TypeDatabaseParserContext; + +QT_FORWARD_DECLARE_CLASS(QDebug) + +int getMaxTypeIndex(); + +struct VersionRange +{ + bool isNull() const + { + return since.majorVersion() == 0 && since.minorVersion() == 0 + && until.majorVersion() == 9999 && until.minorVersion() == 9999; + } + + QVersionNumber since{0, 0}; + QVersionNumber until{9999, 9999}; +}; + +struct TypeRejection +{ + enum MatchType + { + ExcludeClass, // Match className only + Function, // Match className and function name + Field, // Match className and field name + Enum, // Match className and enum name + ArgumentType, // Match className and argument type + ReturnType // Match className and return type + }; + + QRegularExpression className; + QRegularExpression pattern; + MatchType matchType = ExcludeClass; + bool generate; // Current type system + mutable bool matched = false; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const TypeRejection &r); +#endif + +class TypeDatabase +{ + TypeDatabase(); +public: + Q_DISABLE_COPY_MOVE(TypeDatabase) + + ~TypeDatabase(); + + static QList<OptionDescription> options(); + std::shared_ptr<OptionsParser> createOptionsParser(); + + /** + * Return the type system instance. + * \param newInstance This parameter is useful just for unit testing, because singletons causes + * too many side effects on unit testing. + */ + static TypeDatabase *instance(bool newInstance = false); + + static QString normalizedSignature(const QString &signature); + static QString normalizedAddedFunctionSignature(const QString &signature); + + QStringList requiredTargetImports() const; + + void addRequiredTargetImport(const QString &moduleName); + + QStringList typesystemKeywords() const; + + IncludeList extraIncludes(const QString &className) const; + + const QStringList &forceProcessSystemIncludes() const; + void addForceProcessSystemInclude(const QString &name); + + void addInlineNamespaceLookups(const NamespaceTypeEntryCPtr &n); + + PrimitiveTypeEntryPtr findPrimitiveType(const QString &name) const; + ComplexTypeEntryPtr findComplexType(const QString &name) const; + ObjectTypeEntryPtr findObjectType(const QString &name) const; + NamespaceTypeEntryList findNamespaceTypes(const QString &name) const; + NamespaceTypeEntryPtr findNamespaceType(const QString &name, const QString &fileName = QString()) const; + ContainerTypeEntryPtr findContainerType(const QString &name) const; + FunctionTypeEntryPtr findFunctionType(const QString &name) const; + TypeSystemTypeEntryCPtr findTypeSystemType(const QString &name) const; + TypeSystemTypeEntryCPtr defaultTypeSystemType() const; + QString loadedTypeSystemNames() const; + QString defaultPackageName() const; + + TypeEntryPtr findType(const QString &name) const; + TypeEntryCList findTypes(const QString &name) const; + TypeEntryCList findCppTypes(const QString &name) const; + + const TypeEntryMultiMap &entries() const; + const TypedefEntryMap &typedefEntries() const; + + PrimitiveTypeEntryCList primitiveTypes() const; + + ContainerTypeEntryCList containerTypes() const; + + SmartPointerTypeEntryList smartPointerTypes() const; + + void addRejection(const TypeRejection &); + bool isClassRejected(const QString &className, QString *reason = nullptr) const; + bool isFunctionRejected(const QString &className, const QString &functionName, + QString *reason = nullptr) const; + bool isFieldRejected(const QString &className, const QString &fieldName, + QString *reason = nullptr) const; + bool isEnumRejected(const QString &className, const QString &enumName, + QString *reason = nullptr) const; + bool isArgumentTypeRejected(const QString &className, const QString &typeName, + QString *reason = nullptr) const; + bool isReturnTypeRejected(const QString &className, const QString &typeName, + QString *reason = nullptr) const; + + bool addType(const TypeEntryPtr &e, QString *errorMessage = nullptr); + ConstantValueTypeEntryPtr addConstantValueTypeEntry(const QString &value, + const TypeEntryCPtr &parent); + void addTypeSystemType(const TypeSystemTypeEntryCPtr &e); + + static ComplexTypeEntryPtr + initializeTypeDefEntry(const TypedefEntryPtr &typedefEntry, + const ComplexTypeEntryCPtr &source); + + FlagsTypeEntryPtr findFlagsType(const QString &name) const; + void addFlagsType(const FlagsTypeEntryPtr &fte); + + TemplateEntryPtr findTemplate(const QString &name) const; + + void addTemplate(const TemplateEntryPtr &t); + void addTemplate(const QString &name, const QString &code); + + AddedFunctionList globalUserFunctions() const; + + void addGlobalUserFunctions(const AddedFunctionList &functions); + + AddedFunctionList findGlobalUserFunctions(const QString &name) const; + + void addGlobalUserFunctionModifications(const FunctionModificationList &functionModifications); + + FunctionModificationList + globalFunctionModifications(const QStringList &signatures) const; + + bool addSuppressedWarning(const QString &warning, bool generate, QString *errorMessage); + + bool isSuppressedWarning(QStringView s) const; + + static QString globalNamespaceClassName(const TypeEntryCPtr &te); + + // Top level file parsing + bool parseFile(const QString &filename, bool generate = true); + bool parseFile(const std::shared_ptr<TypeDatabaseParserContext> &context, + const QString &filename, const QString ¤tPath, bool generate); + + // Top level QIODevice parsing for tests. + bool parseFile(QIODevice *device, bool generate = true); + bool parseFile(const std::shared_ptr<TypeDatabaseParserContext> &context, + QIODevice *device, bool generate = true); + + static bool setApiVersion(const QString &package, const QString &version); + static void clearApiVersions(); + + static bool checkApiVersion(const QString &package, const VersionRange &vr); + + bool hasDroppedTypeEntries() const; + + bool shouldDropTypeEntry(const QString &fullTypeName) const; + + void setDropTypeEntries(const QStringList &dropTypeEntries); + + QString modifiedTypesystemFilepath(const QString &tsFile, const QString ¤tPath = QString()) const; + + void logUnmatched() const; + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const; +#endif + void formatBuiltinTypes(QDebug debug) const; + +private: + TypeDatabasePrivate *d; +}; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const TypeEntryCPtr &te); +QDebug operator<<(QDebug d, const TypeEntry *te); +QDebug operator<<(QDebug d, const TypeDatabase &db); +#endif +#endif // TYPEDATABASE_H diff --git a/sources/shiboken6/ApiExtractor/typedatabase_p.h b/sources/shiboken6/ApiExtractor/typedatabase_p.h new file mode 100644 index 000000000..fc56c7961 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typedatabase_p.h @@ -0,0 +1,25 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPEDATABASE_P_H +#define TYPEDATABASE_P_H + +#include "typesystem_typedefs.h" +#include "containertypeentry.h" + +#include <QtCore/QHash> +#include <QtCore/QString> + +class TypeDatabase; + +struct TypeDatabaseParserContext +{ + using SmartPointerInstantiations = QHash<SmartPointerTypeEntryPtr, QString>; + using OpaqueContainerHash = QHash<QString, OpaqueContainers>; + + TypeDatabase *db; + SmartPointerInstantiations smartPointerInstantiations; + OpaqueContainerHash opaqueContainerHash; +}; + +#endif // TYPEDATABASE_P_H diff --git a/sources/shiboken6/ApiExtractor/typedatabase_typedefs.h b/sources/shiboken6/ApiExtractor/typedatabase_typedefs.h new file mode 100644 index 000000000..f00c61570 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typedatabase_typedefs.h @@ -0,0 +1,33 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPEDATABASE_TYPEDEFS_H +#define TYPEDATABASE_TYPEDEFS_H + +#include "typesystem_typedefs.h" + +#include <QtCore/QMultiMap> +#include <QtCore/QString> +#include <QtCore/QList> + +using TemplateEntryMap =QMap<QString, TemplateEntryPtr>; + +template <class Key, class Value> +struct QMultiMapConstIteratorRange // A range of iterator for a range-based for loop +{ + using ConstIterator = typename QMultiMap<Key, Value>::const_iterator; + + ConstIterator begin() const { return m_begin; } + ConstIterator end() const { return m_end; } + + ConstIterator m_begin; + ConstIterator m_end; +}; + +using TypeEntryMultiMap = QMultiMap<QString, TypeEntryPtr>; +using TypeEntryMultiMapConstIteratorRange = QMultiMapConstIteratorRange<QString, TypeEntryPtr>; + +using TypeEntryMap = QMap<QString, TypeEntryPtr>; +using TypedefEntryMap = QMap<QString, TypedefEntryPtr>; + +#endif // TYPEDATABASE_TYPEDEFS_H diff --git a/sources/shiboken6/ApiExtractor/typedefentry.h b/sources/shiboken6/ApiExtractor/typedefentry.h new file mode 100644 index 000000000..44646972c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typedefentry.h @@ -0,0 +1,37 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPEDEFENTRY_H +#define TYPEDEFENTRY_H + +#include "complextypeentry.h" + +class TypedefEntryPrivate; + +class TypedefEntry : public ComplexTypeEntry +{ +public: + explicit TypedefEntry(const QString &entryName, + const QString &sourceType, + const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + QString sourceType() const; + void setSourceType(const QString &s); + + TypeEntry *clone() const override; + + ComplexTypeEntryCPtr source() const; + void setSource(const ComplexTypeEntryCPtr &source); + + ComplexTypeEntryPtr target() const; + void setTarget(ComplexTypeEntryPtr target); + +#ifndef QT_NO_DEBUG_STREAM + void formatDebug(QDebug &d) const override; +#endif +protected: + explicit TypedefEntry(TypedefEntryPrivate *d); +}; + +#endif // TYPEDEFENTRY_H diff --git a/sources/shiboken6/ApiExtractor/typeparser.cpp b/sources/shiboken6/ApiExtractor/typeparser.cpp new file mode 100644 index 000000000..11d7bf641 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typeparser.cpp @@ -0,0 +1,292 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "typeparser.h" +#include <typeinfo.h> + +#include <QtCore/QDebug> +#include <QtCore/QStack> +#include <QtCore/QTextStream> + +using namespace Qt::StringLiterals; + +class Scanner +{ +public: + enum Token { + StarToken, + AmpersandToken, + LessThanToken, + ColonToken, + CommaToken, + OpenParenToken, + CloseParenToken, + SquareBegin, + SquareEnd, + GreaterThanToken, + + ConstToken, + VolatileToken, + Identifier, + NoToken, + InvalidToken + }; + + Scanner(const QString &s) + : m_pos(0), m_length(s.length()), m_tokenStart(-1), m_chars(s.constData()) + { + } + + Token nextToken(QString *errorMessage = nullptr); + QString identifier() const; + + QString msgParseError(const QString &why) const; + +private: + int m_pos; + int m_length; + int m_tokenStart; + const QChar *m_chars; +}; + +QString Scanner::identifier() const +{ + return QString(m_chars + m_tokenStart, m_pos - m_tokenStart); +} + +Scanner::Token Scanner::nextToken(QString *errorMessage) +{ + Token tok = NoToken; + + // remove whitespace + while (m_pos < m_length && m_chars[m_pos] == u' ') + ++m_pos; + + m_tokenStart = m_pos; + + while (m_pos < m_length) { + + const QChar &c = m_chars[m_pos]; + + if (tok == NoToken) { + switch (c.toLatin1()) { + case '*': tok = StarToken; break; + case '&': tok = AmpersandToken; break; + case '<': tok = LessThanToken; break; + case '>': tok = GreaterThanToken; break; + case ',': tok = CommaToken; break; + case '(': tok = OpenParenToken; break; + case ')': tok = CloseParenToken; break; + case '[': tok = SquareBegin; break; + case ']' : tok = SquareEnd; break; + case ':': + tok = ColonToken; + Q_ASSERT(m_pos + 1 < m_length); + ++m_pos; + break; + default: + if (c.isLetterOrNumber() || c == u'_') { + tok = Identifier; + } else { + QString message; + QTextStream (&message) << ": Unrecognized character in lexer at " + << m_pos << " : '" << c << '\''; + message = msgParseError(message); + if (errorMessage) + *errorMessage = message; + else + qWarning().noquote().nospace() << message; + return InvalidToken; + } + break; + } + } + + if (tok <= GreaterThanToken) { + ++m_pos; + break; + } + + if (tok == Identifier) { + if (c.isLetterOrNumber() || c == u'_') + ++m_pos; + else + break; + } + } + + if (tok == Identifier) { + switch (m_pos - m_tokenStart) { + case 5: + if (m_chars[m_tokenStart] == u'c' + && m_chars[m_tokenStart + 1] == u'o' + && m_chars[m_tokenStart + 2] == u'n' + && m_chars[m_tokenStart + 3] == u's' + && m_chars[m_tokenStart + 4] == u't') { + tok = ConstToken; + } + break; + case 8: + if (m_chars[m_tokenStart] == u'v' + && m_chars[m_tokenStart + 1] == u'o' + && m_chars[m_tokenStart + 2] == u'l' + && m_chars[m_tokenStart + 3] == u'a' + && m_chars[m_tokenStart + 4] == u't' + && m_chars[m_tokenStart + 5] == u'i' + && m_chars[m_tokenStart + 6] == u'l' + && m_chars[m_tokenStart + 7] == u'e') { + tok = VolatileToken; + } + break; + } + } + + return tok; + +} + +QString Scanner::msgParseError(const QString &why) const +{ + return "TypeParser: Unable to parse \""_L1 + + QString(m_chars, m_length) + "\": "_L1 + why; +} + +TypeInfo TypeParser::parse(const QString &str, QString *errorMessage) +{ + Scanner scanner(str); + + QStack<TypeInfo> stack; + stack.push(TypeInfo()); + + bool colon_prefix = false; + bool in_array = false; + QString array; + bool seenStar = false; + + Scanner::Token tok = scanner.nextToken(errorMessage); + while (tok != Scanner::NoToken) { + if (tok == Scanner::InvalidToken) + return {}; + +// switch (tok) { +// case Scanner::StarToken: printf(" - *\n"); break; +// case Scanner::AmpersandToken: printf(" - &\n"); break; +// case Scanner::LessThanToken: printf(" - <\n"); break; +// case Scanner::GreaterThanToken: printf(" - >\n"); break; +// case Scanner::ColonToken: printf(" - ::\n"); break; +// case Scanner::CommaToken: printf(" - ,\n"); break; +// case Scanner::ConstToken: printf(" - const\n"); break; +// case Scanner::SquareBegin: printf(" - [\n"); break; +// case Scanner::SquareEnd: printf(" - ]\n"); break; +// case Scanner::Identifier: printf(" - '%s'\n", qPrintable(scanner.identifier())); break; +// default: +// break; +// } + + switch (tok) { + + case Scanner::StarToken: + seenStar = true; + stack.top().addIndirection(Indirection::Pointer); + break; + + case Scanner::AmpersandToken: + switch (stack.top().referenceType()) { + case NoReference: + stack.top().setReferenceType(LValueReference); + break; + case LValueReference: + stack.top().setReferenceType(RValueReference); + break; + case RValueReference: + const QString message = scanner.msgParseError("Too many '&' qualifiers"_L1); + if (errorMessage) + *errorMessage = message; + else + qWarning().noquote().nospace() << message; + return {}; + } + break; + case Scanner::LessThanToken: + stack.push(TypeInfo()); + break; + + case Scanner::CommaToken: + { + auto i = stack.pop(); + stack.top().addInstantiation(i); // Add after populating to prevent detach + stack.push(TypeInfo()); + } + break; + + case Scanner::GreaterThanToken: { + auto i = stack.pop(); + stack.top().addInstantiation(i); // Add after populating to prevent detach + } + break; + + case Scanner::ColonToken: + colon_prefix = true; + break; + + case Scanner::ConstToken: + if (seenStar) { // "int *const": Last indirection is const. + auto indirections = stack.top().indirectionsV(); + Q_ASSERT(!indirections.isEmpty()); + indirections[0] = Indirection::ConstPointer; + stack.top().setIndirectionsV(indirections); + } else { + stack.top().setConstant(true); + } + break; + + case Scanner::VolatileToken: + stack.top().setVolatile(true); + break; + + case Scanner::OpenParenToken: // function pointers not supported + case Scanner::CloseParenToken: { + const QString message = scanner.msgParseError("Function pointers are not supported"_L1); + if (errorMessage) + *errorMessage = message; + else + qWarning().noquote().nospace() << message; + return {}; + } + + case Scanner::Identifier: + if (in_array) { + array = scanner.identifier(); + } else if (colon_prefix || stack.top().qualifiedName().isEmpty()) { + stack.top().addName(scanner.identifier()); + colon_prefix = false; + } else { + QStringList qualifiedName = stack.top().qualifiedName(); + qualifiedName.last().append(u' ' + scanner.identifier()); + stack.top().setQualifiedName(qualifiedName); + } + break; + + case Scanner::SquareBegin: + in_array = true; + break; + + case Scanner::SquareEnd: + in_array = false; + stack.top().addArrayElement(array); + break; + + + default: + break; + } + + tok = scanner.nextToken(errorMessage); + } + + if (stack.isEmpty() || stack.constFirst().qualifiedName().isEmpty()) { + *errorMessage = u"Unable to parse type \""_s + str + u"\"."_s; + return {}; + } + return stack.constFirst(); +} diff --git a/sources/shiboken6/ApiExtractor/typeparser.h b/sources/shiboken6/ApiExtractor/typeparser.h new file mode 100644 index 000000000..97634b5db --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typeparser.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPEPARSER_H +#define TYPEPARSER_H + +#include <QtCore/QString> + +class TypeInfo; + +class TypeParser +{ +public: + static TypeInfo parse(const QString &str, QString *errorMessage = nullptr); +}; + +#endif // TYPEPARSER_H diff --git a/sources/shiboken6/ApiExtractor/typesystem.cpp b/sources/shiboken6/ApiExtractor/typesystem.cpp new file mode 100644 index 000000000..99d42b668 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystem.cpp @@ -0,0 +1,2649 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "typesystem.h" +#include "arraytypeentry.h" +#include "codesnip.h" +#include "complextypeentry.h" +#include "configurabletypeentry.h" +#include "constantvaluetypeentry.h" +#include "containertypeentry.h" +#include "customtypenentry.h" +#include "debughelpers_p.h" +#include "enumtypeentry.h" +#include "enumvaluetypeentry.h" +#include "flagstypeentry.h" +#include "functiontypeentry.h" +#include "include.h" +#include "namespacetypeentry.h" +#include "objecttypeentry.h" +#include "primitivetypeentry.h" +#include "pythontypeentry.h" +#include "smartpointertypeentry.h" +#include "templateargumententry.h" +#include "typedefentry.h" +#include "typesystemtypeentry.h" +#include "valuetypeentry.h" +#include "varargstypeentry.h" +#include "voidtypeentry.h" +#include "abstractmetatype.h" +#include "typedatabase.h" +#include "modifications.h" +#include "sourcelocation.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QRegularExpression> +#include <QtCore/QSet> +#include <QtCore/QVarLengthArray> + +using namespace Qt::StringLiterals; + +static QString buildName(const QString &entryName, const TypeEntryCPtr &parent) +{ + return parent == nullptr || parent->type() == TypeEntry::TypeSystemType + ? entryName : parent->name() + u"::"_s + entryName; +} + +// Access private class as 'd', cf macro Q_D() +#define S_D(Class) auto d = static_cast<Class##Private *>(d_func()) + +class TypeEntryPrivate +{ +public: + TypeEntryPrivate(const TypeEntryPrivate &) = default; // Enable copy for cloning. + TypeEntryPrivate &operator=(const TypeEntryPrivate &) = delete; + TypeEntryPrivate(TypeEntryPrivate &&) = delete; + TypeEntryPrivate &operator=(TypeEntryPrivate &&) = delete; + + explicit TypeEntryPrivate(const QString &entryName, TypeEntry::Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + virtual ~TypeEntryPrivate() = default; + + QString shortName() const; + + TypeEntryCPtr m_parent; + QString m_name; // C++ fully qualified + mutable QString m_cachedShortName; // C++ excluding inline namespaces + QString m_entryName; + QString m_targetLangPackage; + mutable QString m_cachedTargetLangName; // "Foo.Bar" + mutable QString m_cachedTargetLangEntryName; // "Bar" + IncludeList m_extraIncludes; + Include m_include; + QVersionNumber m_version; + SourceLocation m_sourceLocation; // XML file + TypeEntry::CodeGeneration m_codeGeneration = TypeEntry::GenerateCode; + TypeEntryPtr m_viewOn; + CustomTypeEntryPtr m_targetLangApiType; + int m_revision = 0; + int m_sbkIndex = 0; + TypeEntry::Type m_type; + bool m_stream = false; + bool m_private = false; + bool m_builtin = false; +}; + +TypeEntryPrivate::TypeEntryPrivate(const QString &entryName, TypeEntry::Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + m_parent(parent), + m_name(buildName(entryName, parent)), + m_entryName(entryName), + m_version(vr), + m_type(t) +{ +} + +TypeEntry::TypeEntry(const QString &entryName, TypeEntry::Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new TypeEntryPrivate(entryName, t, vr, parent)) +{ +} + +TypeEntry::TypeEntry(TypeEntryPrivate *d) : m_d(d) +{ +} + +TypeEntry::~TypeEntry() = default; + +const IncludeList &TypeEntry::extraIncludes() const +{ + return m_d->m_extraIncludes; +} + +void TypeEntry::setExtraIncludes(const IncludeList &includes) +{ + m_d->m_extraIncludes = includes; +} + +void TypeEntry::addExtraInclude(const Include &newInclude) +{ + if (!m_d->m_extraIncludes.contains(newInclude)) + m_d->m_extraIncludes.append(newInclude); +} + +Include TypeEntry::include() const +{ + return m_d->m_include; +} + +void TypeEntry::setInclude(const Include &inc) +{ + // This is a workaround for preventing double inclusion of the QSharedPointer implementation + // header, which does not use header guards. In the previous parser this was not a problem + // because the Q_QDOC define was set, and the implementation header was never included. + if (inc.name().endsWith(u"qsharedpointer_impl.h")) { + QString path = inc.name(); + path.remove(u"_impl"_s); + m_d->m_include = Include(inc.type(), path); + } else { + m_d->m_include = inc; + } +} + +QVersionNumber TypeEntry::version() const +{ + return m_d->m_version; +} + +bool isCppPrimitive(const TypeEntryCPtr &e) +{ + if (!e->isPrimitive()) + return false; + + if (e->type() == TypeEntry::VoidType) + return true; + + PrimitiveTypeEntryCPtr referencedType = basicReferencedTypeEntry(e); + const QString &typeName = referencedType->name(); + return AbstractMetaType::cppPrimitiveTypes().contains(typeName); +} + +TypeEntry::Type TypeEntry::type() const +{ + return m_d->m_type; +} + +TypeEntryCPtr TypeEntry::parent() const +{ + return m_d->m_parent; +} + +void TypeEntry::setParent(const TypeEntryCPtr &p) +{ + m_d->m_parent = p; +} + +bool TypeEntry::isChildOf(const TypeEntryCPtr &p) const +{ + for (auto e = m_d->m_parent; e; e = e->parent()) { + if (e == p) + return true; + } + return false; +} + +TypeSystemTypeEntryCPtr typeSystemTypeEntry(TypeEntryCPtr e) +{ + for (; e; e = e->parent()) { + if (e->type() == TypeEntry::TypeSystemType) + return std::static_pointer_cast<const TypeSystemTypeEntry>(e); + } + return {}; +} + +TypeEntryCPtr targetLangEnclosingEntry(const TypeEntryCPtr &e) +{ + auto result = e->parent(); + while (result && result->type() != TypeEntry::TypeSystemType + && !NamespaceTypeEntry::isVisibleScope(result)) { + result = result->parent(); + } + return result; +} + +bool TypeEntry::isPrimitive() const +{ + return m_d->m_type == PrimitiveType; +} + +bool TypeEntry::isEnum() const +{ + return m_d->m_type == EnumType; +} + +bool TypeEntry::isFlags() const +{ + return m_d->m_type == FlagsType; +} + +bool TypeEntry::isObject() const +{ + return m_d->m_type == ObjectType; +} + +bool TypeEntry::isNamespace() const +{ + return m_d->m_type == NamespaceType; +} + +bool TypeEntry::isContainer() const +{ + return m_d->m_type == ContainerType; +} + +bool TypeEntry::isSmartPointer() const +{ + return m_d->m_type == SmartPointerType; +} + +bool TypeEntry::isUniquePointer() const +{ + if (m_d->m_type != SmartPointerType) + return false; + auto *ste = static_cast<const SmartPointerTypeEntry *>(this); + return ste->smartPointerType() == TypeSystem::SmartPointerType::Unique; +} + +bool TypeEntry::isArray() const +{ + return m_d->m_type == ArrayType; +} + +bool TypeEntry::isTemplateArgument() const +{ + return m_d->m_type == TemplateArgumentType; +} + +bool TypeEntry::isVoid() const +{ + return m_d->m_type == VoidType; +} + +bool TypeEntry::isVarargs() const +{ + return m_d->m_type == VarargsType; +} + +bool TypeEntry::isCustom() const +{ + return m_d->m_type == CustomType || m_d->m_type == PythonType; +} + +bool TypeEntry::isTypeSystem() const +{ + return m_d->m_type == TypeSystemType; +} + +bool TypeEntry::isFunction() const +{ + return m_d->m_type == FunctionType; +} + +bool TypeEntry::isEnumValue() const +{ + return m_d->m_type == EnumValue; +} + +bool TypeEntry::stream() const +{ + return m_d->m_stream; +} + +void TypeEntry::setStream(bool b) +{ + m_d->m_stream = b; +} + +bool TypeEntry::isBuiltIn() const +{ + return m_d->m_builtin; +} + +void TypeEntry::setBuiltIn(bool b) +{ + m_d->m_builtin = b; +} + +bool TypeEntry::isPrivate() const +{ + return m_d->m_private; +} + +void TypeEntry::setPrivate(bool b) +{ + m_d->m_private = b; +} + +QString TypeEntry::name() const +{ + return m_d->m_name; +} + +// Build the C++ name excluding any inline namespaces +// ("std::__1::shared_ptr" -> "std::shared_ptr" +QString TypeEntryPrivate::shortName() const +{ + if (m_cachedShortName.isEmpty()) { + QVarLengthArray<TypeEntryCPtr > parents; + bool foundInlineNamespace = false; + for (auto p = m_parent; p != nullptr && p->type() != TypeEntry::TypeSystemType; p = p->parent()) { + if (p->type() == TypeEntry::NamespaceType + && std::static_pointer_cast<const NamespaceTypeEntry>(p)->isInlineNamespace()) { + foundInlineNamespace = true; + } else { + parents.append(p); + } + } + if (foundInlineNamespace) { + m_cachedShortName.reserve(m_name.size()); + for (auto i = parents.size() - 1; i >= 0; --i) { + m_cachedShortName.append(parents.at(i)->entryName()); + m_cachedShortName.append(u"::"_s); + } + m_cachedShortName.append(m_entryName); + } else { + m_cachedShortName = m_name; + } + } + return m_cachedShortName; +} + +QString TypeEntry::shortName() const +{ + return m_d->shortName(); +} + +QString TypeEntry::entryName() const +{ + return m_d->m_entryName; +} + +TypeEntry::CodeGeneration TypeEntry::codeGeneration() const +{ + return m_d->m_codeGeneration; +} + +void TypeEntry::setCodeGeneration(TypeEntry::CodeGeneration cg) +{ + m_d->m_codeGeneration = cg; +} + +bool TypeEntry::generateCode() const +{ + return m_d->m_codeGeneration == GenerateCode; +} + +bool TypeEntry::shouldGenerate() const +{ + return generateCode() && NamespaceTypeEntry::isVisibleScope(this); +} + +int TypeEntry::revision() const +{ + return m_d->m_revision; +} + +void TypeEntry::setSbkIndex(int i) +{ + m_d->m_sbkIndex = i; +} + +QString TypeEntry::qualifiedCppName() const +{ + return m_d->m_name; +} + +CustomTypeEntryCPtr TypeEntry::targetLangApiType() const +{ + return m_d->m_targetLangApiType; +} + +bool TypeEntry::hasTargetLangApiType() const +{ + return m_d->m_targetLangApiType != nullptr; +} + +void TypeEntry::setTargetLangApiType(const CustomTypeEntryPtr &cte) +{ + m_d->m_targetLangApiType = cte; +} + +QString TypeEntry::targetLangApiName() const +{ + return m_d->m_targetLangApiType != nullptr + ? m_d->m_targetLangApiType->name() : m_d->m_name; +} + +QString TypeEntry::targetLangName() const +{ + if (m_d->m_cachedTargetLangName.isEmpty()) + m_d->m_cachedTargetLangName = buildTargetLangName(); + return m_d->m_cachedTargetLangName; +} + +void TypeEntry::setTargetLangName(const QString &n) +{ + m_d->m_cachedTargetLangName = n; +} + +QString TypeEntry::buildTargetLangName() const +{ + QString result = m_d->m_entryName; + for (auto p = parent(); p && p->type() != TypeEntry::TypeSystemType; p = p->parent()) { + if (NamespaceTypeEntry::isVisibleScope(p)) { + if (!result.isEmpty()) + result.prepend(u'.'); + QString n = p->m_d->m_entryName; + n.replace(u"::"_s, u"."_s); // Primitive types may have "std::" + result.prepend(n); + } + } + return result; +} + +bool TypeEntry::setRevisionHelper(int r) +{ + const bool changed = m_d->m_revision != r; + m_d->m_revision = r; + return changed; +} + +int TypeEntry::sbkIndexHelper() const +{ + return m_d->m_sbkIndex; +} + +SourceLocation TypeEntry::sourceLocation() const +{ + return m_d->m_sourceLocation; +} + +void TypeEntry::setSourceLocation(const SourceLocation &sourceLocation) +{ + m_d->m_sourceLocation = sourceLocation; +} + +bool isUserPrimitive(const TypeEntryCPtr &e) +{ + if (!e->isPrimitive()) + return false; + const auto type = basicReferencedTypeEntry(e); + return !isCppPrimitive(type) + && type->qualifiedCppName() != u"std::string"; +} + +bool TypeEntry::isWrapperType() const +{ + return isObject() || isValue() || isSmartPointer(); +} + +bool isCppIntegralPrimitive(const TypeEntryCPtr &e) +{ + if (!isCppPrimitive(e)) + return false; + const auto type = basicReferencedTypeEntry(e); + return AbstractMetaType::cppIntegralTypes().contains(type->qualifiedCppName()); +} + +bool isExtendedCppPrimitive(const TypeEntryCPtr &e) +{ + if (isCppPrimitive(e)) + return true; + if (!e->isPrimitive()) + return false; + const auto type = basicReferencedTypeEntry(e); + const QString &name = type->qualifiedCppName(); + return name == u"std::string" || name == u"std::wstring"; +} + +const TypeEntryPrivate *TypeEntry::d_func() const +{ + return m_d.data(); +} + +TypeEntryPrivate *TypeEntry::d_func() +{ + return m_d.data(); +} + +QString TypeEntry::targetLangEntryName() const +{ + if (m_d->m_cachedTargetLangEntryName.isEmpty()) { + m_d->m_cachedTargetLangEntryName = targetLangName(); + const int lastDot = m_d->m_cachedTargetLangEntryName.lastIndexOf(u'.'); + if (lastDot != -1) + m_d->m_cachedTargetLangEntryName.remove(0, lastDot + 1); + } + return m_d->m_cachedTargetLangEntryName; +} + +QString TypeEntry::targetLangPackage() const +{ + return m_d->m_targetLangPackage; +} + +void TypeEntry::setTargetLangPackage(const QString &p) +{ + m_d->m_targetLangPackage = p; +} + +QString TypeEntry::qualifiedTargetLangName() const +{ + return targetLangPackage() + u'.' + targetLangName(); +} + +bool TypeEntry::isValue() const +{ + return false; +} + +bool TypeEntry::isComplex() const +{ + return false; +} + +TypeEntryPtr TypeEntry::viewOn() const +{ + return m_d->m_viewOn; +} + +void TypeEntry::setViewOn(const TypeEntryPtr &v) +{ + m_d->m_viewOn = v; +} + +TypeEntry *TypeEntry::clone() const +{ + return new TypeEntry(new TypeEntryPrivate(*m_d.data())); +} + +// Take over parameters relevant for typedefs +void TypeEntry::useAsTypedef(const TypeEntryCPtr &source) +{ + // XML Typedefs are in the global namespace for now. + m_d->m_parent = typeSystemTypeEntry(source); + m_d->m_entryName = source->m_d->m_entryName; + m_d->m_name = source->m_d->m_name; + m_d->m_targetLangPackage = source->m_d->m_targetLangPackage; + m_d->m_cachedTargetLangName.clear(); // Clear cached names. + m_d->m_cachedTargetLangEntryName.clear(); + m_d->m_codeGeneration = source->m_d->m_codeGeneration; + m_d->m_version = source->m_d->m_version; +} + +// ----------------- CustomTypeEntry +class CustomTypeEntryPrivate : public TypeEntryPrivate +{ +public: + using TypeEntryPrivate::TypeEntryPrivate; + + QString m_checkFunction; +}; + +CustomTypeEntry::CustomTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new CustomTypeEntryPrivate(entryName, CustomType, vr, parent)) +{ +} + +CustomTypeEntry::CustomTypeEntry(TypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +TypeEntry *CustomTypeEntry::clone() const +{ + S_D(const CustomTypeEntry); + return new CustomTypeEntry(new CustomTypeEntryPrivate(*d)); +} + +bool CustomTypeEntry::hasCheckFunction() const +{ + S_D(const CustomTypeEntry); + return !d->m_checkFunction.isEmpty(); +} + +QString CustomTypeEntry::checkFunction() const +{ + S_D(const CustomTypeEntry); + return d->m_checkFunction; +} + +void CustomTypeEntry::setCheckFunction(const QString &f) +{ + S_D(CustomTypeEntry); + d->m_checkFunction = f; +} + +// ----------------- PythonTypeEntry +class PythonTypeEntryPrivate : public CustomTypeEntryPrivate +{ +public: + using CustomTypeEntryPrivate::CustomTypeEntryPrivate; + explicit PythonTypeEntryPrivate(const QString &entryName, + const QString &checkFunction, + TypeSystem::CPythonType type) : + CustomTypeEntryPrivate(entryName, TypeEntry::PythonType, {}, {}), + m_cPythonType(type) + { + m_checkFunction = checkFunction; + } + + TypeSystem::CPythonType m_cPythonType; +}; + +PythonTypeEntry::PythonTypeEntry(const QString &entryName, + const QString &checkFunction, + TypeSystem::CPythonType type) : + CustomTypeEntry(new PythonTypeEntryPrivate(entryName, checkFunction, type)) +{ +} + +TypeEntry *PythonTypeEntry::clone() const +{ + S_D(const PythonTypeEntry); + return new PythonTypeEntry(new PythonTypeEntryPrivate(*d)); +} + +TypeSystem::CPythonType PythonTypeEntry::cPythonType() const +{ + S_D(const PythonTypeEntry); + return d->m_cPythonType; +} + +PythonTypeEntry::PythonTypeEntry(TypeEntryPrivate *d) : + CustomTypeEntry(d) +{ +} + +// ----------------- TypeSystemTypeEntry +class TypeSystemTypeEntryPrivate : public TypeEntryPrivate +{ +public: + using TypeEntryPrivate::TypeEntryPrivate; + + CodeSnipList m_codeSnips; + TypeSystem::SnakeCase m_snakeCase = TypeSystem::SnakeCase::Disabled; + QString m_subModuleOf; + QString m_namespaceBegin; + QString m_namespaceEnd; +}; + +TypeSystemTypeEntry::TypeSystemTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new TypeSystemTypeEntryPrivate(entryName, TypeSystemType, vr, parent)) +{ +} + +TypeSystemTypeEntry::TypeSystemTypeEntry(TypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +TypeEntry *TypeSystemTypeEntry::clone() const +{ + S_D(const TypeSystemTypeEntry); + return new TypeSystemTypeEntry(new TypeSystemTypeEntryPrivate(*d)); +} + +const CodeSnipList &TypeSystemTypeEntry::codeSnips() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_codeSnips; +} + +CodeSnipList &TypeSystemTypeEntry::codeSnips() +{ + S_D(TypeSystemTypeEntry); + return d->m_codeSnips; +} + +void TypeSystemTypeEntry::addCodeSnip(const CodeSnip &codeSnip) +{ + S_D(TypeSystemTypeEntry); + d->m_codeSnips.append(codeSnip); +} + +QString TypeSystemTypeEntry::subModuleOf() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_subModuleOf; +} + +void TypeSystemTypeEntry::setSubModule(const QString &s) +{ + S_D(TypeSystemTypeEntry); + d->m_subModuleOf = s; +} + +const QString &TypeSystemTypeEntry::namespaceBegin() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_namespaceBegin; +} + +void TypeSystemTypeEntry::setNamespaceBegin(const QString &p) +{ + S_D(TypeSystemTypeEntry); + d->m_namespaceBegin = p; +} + +const QString &TypeSystemTypeEntry::namespaceEnd() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_namespaceEnd; +} + +void TypeSystemTypeEntry::setNamespaceEnd(const QString &n) +{ + S_D(TypeSystemTypeEntry); + d->m_namespaceEnd = n; +} + +TypeSystem::SnakeCase TypeSystemTypeEntry::snakeCase() const +{ + S_D(const TypeSystemTypeEntry); + return d->m_snakeCase; +} + +void TypeSystemTypeEntry::setSnakeCase(TypeSystem::SnakeCase sc) +{ + S_D(TypeSystemTypeEntry); + d->m_snakeCase = sc; +} + +// ----------------- VoidTypeEntry +VoidTypeEntry::VoidTypeEntry() : + TypeEntry(u"void"_s, VoidType, QVersionNumber(0, 0), nullptr) +{ +} + +VoidTypeEntry::VoidTypeEntry(TypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +TypeEntry *VoidTypeEntry::clone() const +{ + return new VoidTypeEntry(new TypeEntryPrivate(*d_func())); +} + +VarargsTypeEntry::VarargsTypeEntry() : + TypeEntry(u"..."_s, VarargsType, QVersionNumber(0, 0), nullptr) +{ +} + +// ----------------- VarargsTypeEntry +TypeEntry *VarargsTypeEntry::clone() const +{ + return new VarargsTypeEntry(new TypeEntryPrivate(*d_func())); +} + +VarargsTypeEntry::VarargsTypeEntry(TypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +// ----------------- TemplateArgumentEntry +class TemplateArgumentEntryPrivate : public TypeEntryPrivate +{ +public: + using TypeEntryPrivate::TypeEntryPrivate; + + int m_ordinal = 0; +}; + +TemplateArgumentEntry::TemplateArgumentEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new TemplateArgumentEntryPrivate(entryName, TemplateArgumentType, vr, parent)) +{ +} + +int TemplateArgumentEntry::ordinal() const +{ + S_D(const TemplateArgumentEntry); + return d->m_ordinal; +} + +void TemplateArgumentEntry::setOrdinal(int o) +{ + S_D(TemplateArgumentEntry); + d->m_ordinal = o; +} + +TypeEntry *TemplateArgumentEntry::clone() const +{ + S_D(const TemplateArgumentEntry); + return new TemplateArgumentEntry(new TemplateArgumentEntryPrivate(*d)); +} + +TemplateArgumentEntry::TemplateArgumentEntry(TemplateArgumentEntryPrivate *d) : + TypeEntry(d) +{ +} + +// ----------------- ArrayTypeEntry +class ArrayTypeEntryPrivate : public TypeEntryPrivate +{ +public: + explicit ArrayTypeEntryPrivate(const TypeEntryCPtr &nested_type, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntryPrivate(u"Array"_s, TypeEntry::ArrayType, vr, parent), + m_nestedType(nested_type) + { + } + + TypeEntryCPtr m_nestedType; +}; + +ArrayTypeEntry::ArrayTypeEntry(const TypeEntryCPtr &nested_type, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new ArrayTypeEntryPrivate(nested_type, vr, parent)) +{ + Q_ASSERT(nested_type); +} + +void ArrayTypeEntry::setNestedTypeEntry(const TypeEntryPtr &nested) +{ + S_D(ArrayTypeEntry); + d->m_nestedType = nested; +} + +TypeEntryCPtr ArrayTypeEntry::nestedTypeEntry() const +{ + S_D(const ArrayTypeEntry); + return d->m_nestedType; +} + +QString ArrayTypeEntry::buildTargetLangName() const +{ + S_D(const ArrayTypeEntry); + return d->m_nestedType->targetLangName() + u"[]"_s; +} + +TypeEntry *ArrayTypeEntry::clone() const +{ + S_D(const ArrayTypeEntry); + return new ArrayTypeEntry(new ArrayTypeEntryPrivate(*d)); +} + +ArrayTypeEntry::ArrayTypeEntry(ArrayTypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +// ----------------- PrimitiveTypeEntry +class PrimitiveTypeEntryPrivate : public TypeEntryPrivate +{ +public: + PrimitiveTypeEntryPrivate(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntryPrivate(entryName, TypeEntry::PrimitiveType, vr, parent), + m_preferredTargetLangType(true) + { + } + + QString m_defaultConstructor; + CustomConversionPtr m_customConversion; + PrimitiveTypeEntryPtr m_referencedTypeEntry; + uint m_preferredTargetLangType : 1; +}; + +PrimitiveTypeEntry::PrimitiveTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new PrimitiveTypeEntryPrivate(entryName, vr, parent)) +{ +} + +QString PrimitiveTypeEntry::defaultConstructor() const +{ + S_D(const PrimitiveTypeEntry); + return d->m_defaultConstructor; +} + +void PrimitiveTypeEntry::setDefaultConstructor(const QString &defaultConstructor) +{ + S_D(PrimitiveTypeEntry); + d->m_defaultConstructor = defaultConstructor; +} + +bool PrimitiveTypeEntry::hasDefaultConstructor() const +{ + S_D(const PrimitiveTypeEntry); + return !d->m_defaultConstructor.isEmpty(); +} + +PrimitiveTypeEntryPtr PrimitiveTypeEntry::referencedTypeEntry() const +{ + S_D(const PrimitiveTypeEntry); + return d->m_referencedTypeEntry; +} + +void PrimitiveTypeEntry::setReferencedTypeEntry(PrimitiveTypeEntryPtr referencedTypeEntry) +{ + S_D(PrimitiveTypeEntry); + d->m_referencedTypeEntry = referencedTypeEntry; +} + +PrimitiveTypeEntryCPtr basicReferencedTypeEntry(const PrimitiveTypeEntryCPtr &e) +{ + auto result = e; + while (auto referenced = result->referencedTypeEntry()) + result = referenced; + return result; +} + +PrimitiveTypeEntryCPtr basicReferencedTypeEntry(const TypeEntryCPtr &e) +{ + Q_ASSERT(e->isPrimitive()); + return basicReferencedTypeEntry(std::static_pointer_cast<const PrimitiveTypeEntry>(e)); +} + +PrimitiveTypeEntryCPtr basicReferencedNonBuiltinTypeEntry(const PrimitiveTypeEntryCPtr &e) +{ + auto result = e; + for (; result->referencedTypeEntry() ; result = result->referencedTypeEntry()) { + if (!result->isBuiltIn()) + break; + } + return result; +} + +bool PrimitiveTypeEntry::referencesType() const +{ + S_D(const PrimitiveTypeEntry); + return d->m_referencedTypeEntry != nullptr; +} + +bool PrimitiveTypeEntry::preferredTargetLangType() const +{ + S_D(const PrimitiveTypeEntry); + return d->m_preferredTargetLangType; +} + +void PrimitiveTypeEntry::setPreferredTargetLangType(bool b) +{ + S_D(PrimitiveTypeEntry); + d->m_preferredTargetLangType = b; +} + +bool PrimitiveTypeEntry::hasCustomConversion() const +{ + S_D(const PrimitiveTypeEntry); + return bool(d->m_customConversion); +} + +void PrimitiveTypeEntry::setCustomConversion(const CustomConversionPtr &customConversion) +{ + S_D(PrimitiveTypeEntry); + d->m_customConversion = customConversion; +} + +CustomConversionPtr PrimitiveTypeEntry::customConversion() const +{ + S_D(const PrimitiveTypeEntry); + return d->m_customConversion; +} + +TypeEntry *PrimitiveTypeEntry::clone() const +{ + S_D(const PrimitiveTypeEntry); + return new PrimitiveTypeEntry(new PrimitiveTypeEntryPrivate(*d)); +} + +PrimitiveTypeEntry::PrimitiveTypeEntry(PrimitiveTypeEntryPrivate *d) + : TypeEntry(d) +{ +} + +// ----------------- ConfigurableTypeEntry + +class ConfigurableTypeEntryPrivate : public TypeEntryPrivate +{ +public: + using TypeEntryPrivate::TypeEntryPrivate; + + QString m_configCondition; +}; + +ConfigurableTypeEntry::ConfigurableTypeEntry(const QString &entryName, Type t, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new ConfigurableTypeEntryPrivate(entryName, t, vr, parent)) +{ +} + +ConfigurableTypeEntry::ConfigurableTypeEntry(ConfigurableTypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +TypeEntry *ConfigurableTypeEntry::clone() const +{ + S_D(const ConfigurableTypeEntry); + return new ConfigurableTypeEntry(new ConfigurableTypeEntryPrivate(*d)); +} + +QString ConfigurableTypeEntry::configCondition() const +{ + S_D(const ConfigurableTypeEntry); + return d->m_configCondition; +} + +void ConfigurableTypeEntry::setConfigCondition(const QString &c) +{ + S_D(ConfigurableTypeEntry); + d->m_configCondition = c; + if (!d->m_configCondition.startsWith(u'#')) + d->m_configCondition.prepend(u"#if "); +} + +bool ConfigurableTypeEntry::hasConfigCondition() const +{ + S_D(const ConfigurableTypeEntry); + return !d->m_configCondition.isEmpty(); +} + +// ----------------- EnumTypeEntry +class EnumTypeEntryPrivate : public ConfigurableTypeEntryPrivate +{ +public: + using ConfigurableTypeEntryPrivate::ConfigurableTypeEntryPrivate; + + EnumValueTypeEntryCPtr m_nullValue; + QStringList m_rejectedEnums; + FlagsTypeEntryPtr m_flags; + QString m_cppType; + QString m_docFile; + TypeSystem::PythonEnumType m_pythonEnumType = TypeSystem::PythonEnumType::Unspecified; +}; + +EnumTypeEntry::EnumTypeEntry(const QString &entryName, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ConfigurableTypeEntry(new EnumTypeEntryPrivate(entryName, EnumType, vr, parent)) +{ +} + +TypeSystem::PythonEnumType EnumTypeEntry::pythonEnumType() const +{ + S_D(const EnumTypeEntry); + return d->m_pythonEnumType; +} + +void EnumTypeEntry::setPythonEnumType(TypeSystem::PythonEnumType t) +{ + S_D(EnumTypeEntry); + d->m_pythonEnumType = t; +} + +QString EnumTypeEntry::targetLangQualifier() const +{ + const QString q = qualifier(); + if (!q.isEmpty()) { + if (auto te = TypeDatabase::instance()->findType(q)) + return te->targetLangName(); + } + return q; +} + +QString EnumTypeEntry::qualifier() const +{ + auto parentEntry = parent(); + return parentEntry && parentEntry->type() != TypeEntry::TypeSystemType ? + parentEntry->name() : QString(); +} + +EnumValueTypeEntryCPtr EnumTypeEntry::nullValue() const +{ + S_D(const EnumTypeEntry); + return d->m_nullValue; +} + +void EnumTypeEntry::setNullValue(const EnumValueTypeEntryCPtr &n) +{ + S_D(EnumTypeEntry); + d->m_nullValue = n; +} + +void EnumTypeEntry::setFlags(const FlagsTypeEntryPtr &flags) +{ + S_D(EnumTypeEntry); + d->m_flags = flags; +} + +FlagsTypeEntryPtr EnumTypeEntry::flags() const +{ + S_D(const EnumTypeEntry); + return d->m_flags; +} + +QString EnumTypeEntry::cppType() const +{ + S_D(const EnumTypeEntry); + return d->m_cppType; +} + +void EnumTypeEntry::setCppType(const QString &t) +{ + S_D(EnumTypeEntry); + d->m_cppType = t; +} + +bool EnumTypeEntry::isEnumValueRejected(const QString &name) const +{ + S_D(const EnumTypeEntry); + return d->m_rejectedEnums.contains(name); +} + +void EnumTypeEntry::addEnumValueRejection(const QString &name) +{ + S_D(EnumTypeEntry); + d->m_rejectedEnums << name; +} + +QStringList EnumTypeEntry::enumValueRejections() const +{ + S_D(const EnumTypeEntry); + return d->m_rejectedEnums; +} + +QString EnumTypeEntry::docFile() const +{ + S_D(const EnumTypeEntry); + return d->m_docFile; +} + +void EnumTypeEntry::setDocFile(const QString &df) +{ + S_D(EnumTypeEntry); + d->m_docFile = df; +} + +TypeEntry *EnumTypeEntry::clone() const +{ + S_D(const EnumTypeEntry); + return new EnumTypeEntry(new EnumTypeEntryPrivate(*d)); +} + +EnumTypeEntry::EnumTypeEntry(EnumTypeEntryPrivate *d) : + ConfigurableTypeEntry(d) +{ +} + +// ----------------- EnumValueTypeEntryPrivate +class EnumValueTypeEntryPrivate : public TypeEntryPrivate +{ +public: + EnumValueTypeEntryPrivate(const QString &name, const QString &value, + const EnumTypeEntryCPtr &enclosingEnum, + bool isScopedEnum, + const QVersionNumber &vr) : + TypeEntryPrivate(name, TypeEntry::EnumValue, vr, + isScopedEnum ? enclosingEnum : enclosingEnum->parent()), + m_value(value), + m_enclosingEnum(enclosingEnum) + { + } + + QString m_value; + EnumTypeEntryCPtr m_enclosingEnum; +}; + +EnumValueTypeEntry::EnumValueTypeEntry(const QString &name, const QString &value, + const EnumTypeEntryCPtr &enclosingEnum, + bool isScopedEnum, + const QVersionNumber &vr) : + TypeEntry(new EnumValueTypeEntryPrivate(name, value, enclosingEnum, isScopedEnum, vr)) +{ +} + +QString EnumValueTypeEntry::value() const +{ + S_D(const EnumValueTypeEntry); + return d->m_value; +} + +EnumTypeEntryCPtr EnumValueTypeEntry::enclosingEnum() const +{ + S_D(const EnumValueTypeEntry); + return d->m_enclosingEnum; +} + +TypeEntry *EnumValueTypeEntry::clone() const +{ + S_D(const EnumValueTypeEntry); + return new EnumValueTypeEntry(new EnumValueTypeEntryPrivate(*d)); +} + +EnumValueTypeEntry::EnumValueTypeEntry(EnumValueTypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +// ----------------- FlagsTypeEntry +class FlagsTypeEntryPrivate : public TypeEntryPrivate +{ +public: + using TypeEntryPrivate::TypeEntryPrivate; + + QString m_originalName; + QString m_flagsName; + EnumTypeEntryPtr m_enum; +}; + +FlagsTypeEntry::FlagsTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new FlagsTypeEntryPrivate(entryName, FlagsType, vr, parent)) +{ +} + +QString FlagsTypeEntry::buildTargetLangName() const +{ + S_D(const FlagsTypeEntry); + QString on = d->m_originalName; + on.replace(u"::"_s, u"."_s); + return on; +} + +FlagsTypeEntry::FlagsTypeEntry(FlagsTypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +QString FlagsTypeEntry::originalName() const +{ + S_D(const FlagsTypeEntry); + return d->m_originalName; +} + +void FlagsTypeEntry::setOriginalName(const QString &s) +{ + S_D(FlagsTypeEntry); + d->m_originalName = s; +} + +QString FlagsTypeEntry::flagsName() const +{ + S_D(const FlagsTypeEntry); + return d->m_flagsName; +} + +void FlagsTypeEntry::setFlagsName(const QString &name) +{ + S_D(FlagsTypeEntry); + d->m_flagsName = name; +} + +EnumTypeEntryPtr FlagsTypeEntry::originator() const +{ + S_D(const FlagsTypeEntry); + return d->m_enum; +} + +void FlagsTypeEntry::setOriginator(const EnumTypeEntryPtr &e) +{ + S_D(FlagsTypeEntry); + d->m_enum = e; +} + +TypeEntry *FlagsTypeEntry::clone() const +{ + S_D(const FlagsTypeEntry); + return new FlagsTypeEntry(new FlagsTypeEntryPrivate(*d)); +} + +// ----------------- ConstantValueTypeEntry +ConstantValueTypeEntry::ConstantValueTypeEntry(const QString& name, + const TypeEntryCPtr &parent) : + TypeEntry(name, ConstantValueType, QVersionNumber(0, 0), parent) +{ +} + +ConstantValueTypeEntry::ConstantValueTypeEntry(TypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +// ----------------- ComplexTypeEntry +class ComplexTypeEntryPrivate : public ConfigurableTypeEntryPrivate +{ +public: + ComplexTypeEntryPrivate(const QString &entryName, TypeEntry::Type t, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ConfigurableTypeEntryPrivate(entryName, t, vr, parent), + m_qualifiedCppName(buildName(entryName, parent)), + m_polymorphicBase(false), + m_genericClass(false), + m_deleteInMainThread(false) + { + } + + AddedFunctionList m_addedFunctions; + FunctionModificationList m_functionMods; + CodeSnipList m_codeSnips; + DocModificationList m_docModifications; + DocModificationList m_functionDocModifications; + IncludeList m_argumentIncludes; + QSet<QString> m_generateFunctions; + FieldModificationList m_fieldMods; + QList<TypeSystemProperty> m_properties; + QList<TypeSystemPyMethodDefEntry> m_PyMethodDefEntrys; + QString m_defaultConstructor; + QString m_defaultSuperclass; + QString m_qualifiedCppName; + + uint m_polymorphicBase : 1; + uint m_genericClass : 1; + uint m_deleteInMainThread : 1; + + QString m_polymorphicIdValue; + QString m_polymorphicNameFunction; + QString m_targetType; + ComplexTypeEntry::TypeFlags m_typeFlags; + ComplexTypeEntry::CopyableFlag m_copyableFlag = ComplexTypeEntry::Unknown; + QString m_hashFunction; + + ComplexTypeEntryCPtr m_baseContainerType; + // For class functions + TypeSystem::ExceptionHandling m_exceptionHandling = TypeSystem::ExceptionHandling::Unspecified; + TypeSystem::AllowThread m_allowThread = TypeSystem::AllowThread::Unspecified; + TypeSystem::SnakeCase m_snakeCase = TypeSystem::SnakeCase::Unspecified; + TypeSystem::BoolCast m_operatorBoolMode = TypeSystem::BoolCast::Unspecified; + TypeSystem::BoolCast m_isNullMode = TypeSystem::BoolCast::Unspecified; + TypeSystem::QtMetaTypeRegistration m_qtMetaTypeRegistration = + TypeSystem::QtMetaTypeRegistration::Unspecified; + // Determined by AbstractMetaBuilder from the code model. + bool m_isValueTypeWithCopyConstructorOnly = false; +}; + +ComplexTypeEntry::ComplexTypeEntry(const QString &entryName, TypeEntry::Type t, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ConfigurableTypeEntry(new ComplexTypeEntryPrivate(entryName, t, vr, parent)) +{ +} + +bool ComplexTypeEntry::isComplex() const +{ + return true; +} + +void ComplexTypeEntry::setTypeFlags(TypeFlags flags) +{ + S_D(ComplexTypeEntry); + d->m_typeFlags = flags; +} + +TypeSystem::BoolCast ComplexTypeEntry::operatorBoolMode() const +{ + S_D(const ComplexTypeEntry); + return d->m_operatorBoolMode; +} + +void ComplexTypeEntry::setOperatorBoolMode(TypeSystem::BoolCast b) +{ + S_D(ComplexTypeEntry); + d->m_operatorBoolMode = b; +} + +TypeSystem::BoolCast ComplexTypeEntry::isNullMode() const +{ + S_D(const ComplexTypeEntry); + return d->m_isNullMode; +} + +void ComplexTypeEntry::setIsNullMode(TypeSystem::BoolCast b) +{ + S_D(ComplexTypeEntry); + d->m_isNullMode = b; +} + +ComplexTypeEntry::TypeFlags ComplexTypeEntry::typeFlags() const +{ + S_D(const ComplexTypeEntry); + return d->m_typeFlags; +} + +FunctionModificationList ComplexTypeEntry::functionModifications() const +{ + S_D(const ComplexTypeEntry); + return d->m_functionMods; +} + +void ComplexTypeEntry::setFunctionModifications(const FunctionModificationList &functionModifications) +{ + S_D(ComplexTypeEntry); + d->m_functionMods = functionModifications; +} + +void ComplexTypeEntry::addFunctionModification(const FunctionModification &functionModification) +{ + S_D(ComplexTypeEntry); + d->m_functionMods << functionModification; +} + +FunctionModificationList + ComplexTypeEntry::functionModifications(const QStringList &signatures) const +{ + S_D(const ComplexTypeEntry); + FunctionModificationList lst; + for (const auto &mod : std::as_const(d->m_functionMods)) { + if (mod.matches(signatures)) + lst << mod; + } + return lst; +} + +const CodeSnipList &ComplexTypeEntry::codeSnips() const +{ + S_D(const ComplexTypeEntry); + return d->m_codeSnips; +} + +CodeSnipList &ComplexTypeEntry::codeSnips() +{ + S_D(ComplexTypeEntry); + return d->m_codeSnips; +} + +void ComplexTypeEntry::setCodeSnips(const CodeSnipList &codeSnips) +{ + S_D(ComplexTypeEntry); + d->m_codeSnips = codeSnips; +} + +void ComplexTypeEntry::addCodeSnip(const CodeSnip &codeSnip) +{ + S_D(ComplexTypeEntry); + d->m_codeSnips << codeSnip; +} + +void ComplexTypeEntry::setDocModification(const DocModificationList &docMods) +{ + S_D(ComplexTypeEntry); + for (const auto &m : docMods) { + if (m.signature().isEmpty()) + d->m_docModifications << m; + else + d->m_functionDocModifications << m; + } +} + +DocModificationList ComplexTypeEntry::docModifications() const +{ + S_D(const ComplexTypeEntry); + return d->m_docModifications; +} + +DocModificationList ComplexTypeEntry::functionDocModifications() const +{ + S_D(const ComplexTypeEntry); + return d->m_functionDocModifications; +} + +const IncludeList &ComplexTypeEntry::argumentIncludes() const +{ + S_D(const ComplexTypeEntry); + return d->m_argumentIncludes; +} + +void ComplexTypeEntry::addArgumentInclude(const Include &newInclude) +{ + S_D(ComplexTypeEntry); + IncludeGroup::appendInclude(newInclude, &d->m_argumentIncludes); +} + +AddedFunctionList ComplexTypeEntry::addedFunctions() const +{ + S_D(const ComplexTypeEntry); + return d->m_addedFunctions; +} + +void ComplexTypeEntry::setAddedFunctions(const AddedFunctionList &addedFunctions) +{ + S_D(ComplexTypeEntry); + d->m_addedFunctions = addedFunctions; +} + +void ComplexTypeEntry::addNewFunction(const AddedFunctionPtr &addedFunction) +{ + S_D(ComplexTypeEntry); + d->m_addedFunctions << addedFunction; +} + +const QList<TypeSystemPyMethodDefEntry> &ComplexTypeEntry::addedPyMethodDefEntrys() const +{ + S_D(const ComplexTypeEntry); + return d->m_PyMethodDefEntrys; +} + +void ComplexTypeEntry::addPyMethodDef(const TypeSystemPyMethodDefEntry &p) +{ + S_D(ComplexTypeEntry); + d->m_PyMethodDefEntrys.append(p); +} + +const QSet<QString> &ComplexTypeEntry::generateFunctions() const +{ + S_D(const ComplexTypeEntry); + return d->m_generateFunctions; +} + +void ComplexTypeEntry::setGenerateFunctions(const QSet<QString> &f) +{ + S_D(ComplexTypeEntry); + d->m_generateFunctions = f; +} + +void ComplexTypeEntry::setFieldModifications(const FieldModificationList &mods) +{ + S_D(ComplexTypeEntry); + d->m_fieldMods = mods; +} + +FieldModificationList ComplexTypeEntry::fieldModifications() const +{ + S_D(const ComplexTypeEntry); + return d->m_fieldMods; +} + +const QList<TypeSystemProperty> &ComplexTypeEntry::properties() const +{ + S_D(const ComplexTypeEntry); + return d->m_properties; +} + +void ComplexTypeEntry::addProperty(const TypeSystemProperty &p) +{ + S_D(ComplexTypeEntry); + d->m_properties.append(p); +} + +QString ComplexTypeEntry::defaultSuperclass() const +{ + S_D(const ComplexTypeEntry); + return d->m_defaultSuperclass; +} + +void ComplexTypeEntry::setDefaultSuperclass(const QString &sc) +{ + S_D(ComplexTypeEntry); + d->m_defaultSuperclass = sc; +} + +QString ComplexTypeEntry::qualifiedCppName() const +{ + S_D(const ComplexTypeEntry); + return d->m_qualifiedCppName; +} + +void ComplexTypeEntry::setIsPolymorphicBase(bool on) +{ + S_D(ComplexTypeEntry); + d->m_polymorphicBase = on; +} + +bool ComplexTypeEntry::isPolymorphicBase() const +{ + S_D(const ComplexTypeEntry); + return d->m_polymorphicBase; +} + +void ComplexTypeEntry::setPolymorphicIdValue(const QString &value) +{ + S_D(ComplexTypeEntry); + d->m_polymorphicIdValue = value; +} + +QString ComplexTypeEntry::polymorphicIdValue() const +{ + S_D(const ComplexTypeEntry); + return d->m_polymorphicIdValue; +} + +QString ComplexTypeEntry::polymorphicNameFunction() const +{ + S_D(const ComplexTypeEntry); + return d->m_polymorphicNameFunction; +} + +void ComplexTypeEntry::setPolymorphicNameFunction(const QString &n) +{ + S_D(ComplexTypeEntry); + d->m_polymorphicNameFunction = n; +} + +QString ComplexTypeEntry::targetType() const +{ + S_D(const ComplexTypeEntry); + return d->m_targetType; +} + +void ComplexTypeEntry::setTargetType(const QString &code) +{ + S_D(ComplexTypeEntry); + d->m_targetType = code; +} + +bool ComplexTypeEntry::isGenericClass() const +{ + S_D(const ComplexTypeEntry); + return d->m_genericClass; +} + +void ComplexTypeEntry::setGenericClass(bool isGeneric) +{ + S_D(ComplexTypeEntry); + d->m_genericClass = isGeneric; +} + +bool ComplexTypeEntry::deleteInMainThread() const +{ + S_D(const ComplexTypeEntry); + return d->m_deleteInMainThread; +} + +void ComplexTypeEntry::setDeleteInMainThread(bool dmt) +{ + S_D(ComplexTypeEntry); + d->m_deleteInMainThread = dmt; +} + +ComplexTypeEntry::CopyableFlag ComplexTypeEntry::copyable() const +{ + S_D(const ComplexTypeEntry); + return d->m_copyableFlag; +} + +void ComplexTypeEntry::setCopyable(ComplexTypeEntry::CopyableFlag flag) +{ + S_D(ComplexTypeEntry); + d->m_copyableFlag = flag; +} + +TypeSystem::QtMetaTypeRegistration ComplexTypeEntry::qtMetaTypeRegistration() const +{ + S_D(const ComplexTypeEntry); + return d->m_qtMetaTypeRegistration; +} + +void ComplexTypeEntry::setQtMetaTypeRegistration(TypeSystem::QtMetaTypeRegistration r) +{ + S_D(ComplexTypeEntry); + d->m_qtMetaTypeRegistration = r; +} + +QString ComplexTypeEntry::hashFunction() const +{ + S_D(const ComplexTypeEntry); + return d->m_hashFunction; +} + +void ComplexTypeEntry::setHashFunction(const QString &hashFunction) +{ + S_D(ComplexTypeEntry); + d->m_hashFunction = hashFunction; +} + +void ComplexTypeEntry::setBaseContainerType(const ComplexTypeEntryCPtr &baseContainer) +{ + S_D(ComplexTypeEntry); + d->m_baseContainerType = baseContainer; +} + +ComplexTypeEntryCPtr ComplexTypeEntry::baseContainerType() const +{ + S_D(const ComplexTypeEntry); + return d->m_baseContainerType; +} + +TypeSystem::ExceptionHandling ComplexTypeEntry::exceptionHandling() const +{ + S_D(const ComplexTypeEntry); + return d->m_exceptionHandling; +} + +void ComplexTypeEntry::setExceptionHandling(TypeSystem::ExceptionHandling e) +{ + S_D(ComplexTypeEntry); + d->m_exceptionHandling = e; +} + +TypeSystem::AllowThread ComplexTypeEntry::allowThread() const +{ + S_D(const ComplexTypeEntry); + return d->m_allowThread; +} + +void ComplexTypeEntry::setAllowThread(TypeSystem::AllowThread allowThread) +{ + S_D(ComplexTypeEntry); + d->m_allowThread = allowThread; +} + +void ComplexTypeEntry::setDefaultConstructor(const QString& defaultConstructor) +{ + S_D(ComplexTypeEntry); + d->m_defaultConstructor = defaultConstructor; +} + +QString ComplexTypeEntry::defaultConstructor() const +{ + S_D(const ComplexTypeEntry); + return d->m_defaultConstructor; +} + +bool ComplexTypeEntry::hasDefaultConstructor() const +{ + S_D(const ComplexTypeEntry); + return !d->m_defaultConstructor.isEmpty(); +} + +TypeSystem::SnakeCase ComplexTypeEntry::snakeCase() const +{ + S_D(const ComplexTypeEntry); + return d->m_snakeCase; +} + +void ComplexTypeEntry::setSnakeCase(TypeSystem::SnakeCase sc) +{ + S_D(ComplexTypeEntry); + d->m_snakeCase = sc; +} + +bool ComplexTypeEntry::isValueTypeWithCopyConstructorOnly() const +{ + S_D(const ComplexTypeEntry); + return d->m_isValueTypeWithCopyConstructorOnly; +} + +void ComplexTypeEntry::setValueTypeWithCopyConstructorOnly(bool v) +{ + S_D(ComplexTypeEntry); + d->m_isValueTypeWithCopyConstructorOnly = v; +} + +// FIXME PYSIDE 7: Remove this and make "true" the default +static bool parentManagementEnabled = false; + +bool ComplexTypeEntry::isParentManagementEnabled() +{ + return parentManagementEnabled; +} + +void ComplexTypeEntry::setParentManagementEnabled(bool e) +{ + parentManagementEnabled = e; +} + +TypeEntry *ComplexTypeEntry::clone() const +{ + S_D(const ComplexTypeEntry); + return new ComplexTypeEntry(new ComplexTypeEntryPrivate(*d)); +} + +// Take over parameters relevant for typedefs +void ComplexTypeEntry::useAsTypedef(const ComplexTypeEntryCPtr &source) +{ + S_D(ComplexTypeEntry); + TypeEntry::useAsTypedef(source); + d->m_qualifiedCppName = source->qualifiedCppName(); + d->m_targetType = source->targetType(); + d->m_typeFlags.setFlag(ComplexTypeEntry::Typedef); +} + +ComplexTypeEntry::ComplexTypeEntry(ComplexTypeEntryPrivate *d) : + ConfigurableTypeEntry(d) +{ +} + +TypeEntry *ConstantValueTypeEntry::clone() const +{ + return new ConstantValueTypeEntry(new TypeEntryPrivate(*d_func())); +} + + +// ----------------- TypedefEntry +/* A typedef entry allows for specifying template specializations in the + * typesystem XML file. */ +class TypedefEntryPrivate : public ComplexTypeEntryPrivate +{ +public: + TypedefEntryPrivate(const QString &entryName, + const QString &sourceType, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntryPrivate(entryName, TypeEntry::TypedefType, + vr, parent), + m_sourceType(sourceType) + { + } + + QString m_sourceType; + ComplexTypeEntryCPtr m_source; + ComplexTypeEntryPtr m_target; +}; + +TypedefEntry::TypedefEntry(const QString &entryName, const QString &sourceType, + const QVersionNumber &vr, const TypeEntryCPtr &parent) : + ComplexTypeEntry(new TypedefEntryPrivate(entryName, sourceType, vr, parent)) +{ +} + +QString TypedefEntry::sourceType() const +{ + S_D(const TypedefEntry); + return d->m_sourceType; +} + +void TypedefEntry::setSourceType(const QString &s) +{ + S_D(TypedefEntry); + d->m_sourceType =s; +} + +TypeEntry *TypedefEntry::clone() const +{ + S_D(const TypedefEntry); + return new TypedefEntry(new TypedefEntryPrivate(*d)); +} + +ComplexTypeEntryCPtr TypedefEntry::source() const +{ + S_D(const TypedefEntry); + return d->m_source; +} + +void TypedefEntry::setSource(const ComplexTypeEntryCPtr &source) +{ + S_D(TypedefEntry); + d->m_source = source; +} + +ComplexTypeEntryPtr TypedefEntry::target() const +{ + S_D(const TypedefEntry); + return d->m_target; +} + +void TypedefEntry::setTarget(ComplexTypeEntryPtr target) +{ + S_D(TypedefEntry); + d->m_target = target; +} + +TypedefEntry::TypedefEntry(TypedefEntryPrivate *d) : + ComplexTypeEntry(d) +{ +} + +// ----------------- ContainerTypeEntry +class ContainerTypeEntryPrivate : public ComplexTypeEntryPrivate +{ +public: + ContainerTypeEntryPrivate(const QString &entryName, + ContainerTypeEntry::ContainerKind containerKind, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntryPrivate(entryName, TypeEntry::ContainerType, vr, parent), + m_containerKind(containerKind) + { + } + + OpaqueContainers::const_iterator findOpaqueContainer(const QStringList &instantiations) const + { + return std::find_if(m_opaqueContainers.cbegin(), m_opaqueContainers.cend(), + [&instantiations](const OpaqueContainer &r) { + return r.instantiations == instantiations; + }); + } + + OpaqueContainers m_opaqueContainers; + CustomConversionPtr m_customConversion; + ContainerTypeEntry::ContainerKind m_containerKind; +}; + +QString OpaqueContainer::templateParameters() const +{ + QString result; + result += u'<'; + for (qsizetype i = 0, size = instantiations.size(); i < size; ++i) { + if (i) + result += u','; + result += instantiations.at(i); + } + result += u'>'; + return result; +} + +ContainerTypeEntry::ContainerTypeEntry(const QString &entryName, ContainerKind containerKind, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntry(new ContainerTypeEntryPrivate(entryName, containerKind, vr, parent)) +{ + setCodeGeneration(GenerateForSubclass); +} + +ContainerTypeEntry::ContainerKind ContainerTypeEntry::containerKind() const +{ + S_D(const ContainerTypeEntry); + return d->m_containerKind; +} + +qsizetype ContainerTypeEntry::templateParameterCount() const +{ + S_D(const ContainerTypeEntry); + qsizetype result = 1; + switch (d->m_containerKind) { + case MapContainer: + case MultiMapContainer: + case PairContainer: + case SpanContainer: + result = 2; + break; + case ListContainer: + case SetContainer: + break; + } + return result; +} + +const OpaqueContainers &ContainerTypeEntry::opaqueContainers() const +{ + S_D(const ContainerTypeEntry); + return d->m_opaqueContainers; +} + +void ContainerTypeEntry::appendOpaqueContainers(const OpaqueContainers &l) +{ + S_D(ContainerTypeEntry); + d->m_opaqueContainers.append(l); +} + +bool ContainerTypeEntry::generateOpaqueContainer(const QStringList &instantiations) const +{ + S_D(const ContainerTypeEntry); + return d->findOpaqueContainer(instantiations) != d->m_opaqueContainers.cend(); +} + +QString ContainerTypeEntry::opaqueContainerName(const QStringList &instantiations) const +{ + S_D(const ContainerTypeEntry); + const auto it = d->findOpaqueContainer(instantiations); + return it != d->m_opaqueContainers.cend() ? it->name : QString{}; +} + +bool ContainerTypeEntry::hasCustomConversion() const +{ + S_D(const ContainerTypeEntry); + return bool(d->m_customConversion); +} + +void ContainerTypeEntry::setCustomConversion(const CustomConversionPtr &customConversion) +{ + S_D(ContainerTypeEntry); + d->m_customConversion = customConversion; +} + +CustomConversionPtr ContainerTypeEntry::customConversion() const +{ + S_D(const ContainerTypeEntry); + return d->m_customConversion; +} + +TypeEntry *ContainerTypeEntry::clone() const +{ + S_D(const ContainerTypeEntry); + return new ContainerTypeEntry(new ContainerTypeEntryPrivate(*d)); +} + +ContainerTypeEntry::ContainerTypeEntry(ContainerTypeEntryPrivate *d) : + ComplexTypeEntry(d) +{ +} + +// ----------------- SmartPointerTypeEntry +class SmartPointerTypeEntryPrivate : public ComplexTypeEntryPrivate +{ +public: + SmartPointerTypeEntryPrivate(const QString &entryName, + const QString &getterName, + TypeSystem::SmartPointerType type, + const QString &refCountMethodName, + const QVersionNumber &vr, const TypeEntryCPtr &parent) : + ComplexTypeEntryPrivate(entryName, TypeEntry::SmartPointerType, vr, parent), + m_getterName(getterName), + m_refCountMethodName(refCountMethodName), + m_smartPointerType(type) + { + } + + qsizetype instantiationIndex(const TypeEntryCPtr &t) const; + + QString m_getterName; + QString m_refCountMethodName; + QString m_valueCheckMethod; + QString m_nullCheckMethod; + QString m_resetMethod; + SmartPointerTypeEntry::Instantiations m_instantiations; + TypeSystem::SmartPointerType m_smartPointerType; +}; + +qsizetype SmartPointerTypeEntryPrivate::instantiationIndex(const TypeEntryCPtr &t) const +{ + for (qsizetype i = 0, size = m_instantiations.size(); i < size; ++i) { + if (m_instantiations.at(i).typeEntry == t) + return i; + } + return -1; +} + +SmartPointerTypeEntry::SmartPointerTypeEntry(const QString &entryName, + const QString &getterName, + TypeSystem::SmartPointerType smartPointerType, + const QString &refCountMethodName, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntry(new SmartPointerTypeEntryPrivate(entryName, getterName, smartPointerType, + refCountMethodName, vr, parent)) +{ +} + +TypeSystem::SmartPointerType SmartPointerTypeEntry::smartPointerType() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_smartPointerType; +} + +QString SmartPointerTypeEntry::getter() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_getterName; +} + +QString SmartPointerTypeEntry::refCountMethodName() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_refCountMethodName; +} + +QString SmartPointerTypeEntry::valueCheckMethod() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_valueCheckMethod; +} + +void SmartPointerTypeEntry::setValueCheckMethod(const QString &m) +{ + S_D(SmartPointerTypeEntry); + d->m_valueCheckMethod = m; +} + +QString SmartPointerTypeEntry::nullCheckMethod() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_nullCheckMethod; +} + +void SmartPointerTypeEntry::setNullCheckMethod(const QString &f) +{ + S_D(SmartPointerTypeEntry); + d->m_nullCheckMethod = f; +} + +QString SmartPointerTypeEntry::resetMethod() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_resetMethod; +} + +void SmartPointerTypeEntry::setResetMethod(const QString &f) +{ + S_D(SmartPointerTypeEntry); + d->m_resetMethod = f; +} + +TypeEntry *SmartPointerTypeEntry::clone() const +{ + S_D(const SmartPointerTypeEntry); + return new SmartPointerTypeEntry(new SmartPointerTypeEntryPrivate(*d)); +} + +const SmartPointerTypeEntry::Instantiations &SmartPointerTypeEntry::instantiations() const +{ + S_D(const SmartPointerTypeEntry); + return d->m_instantiations; +} + +void SmartPointerTypeEntry::setInstantiations(const Instantiations &i) +{ + S_D(SmartPointerTypeEntry); + d->m_instantiations = i; +} + +SmartPointerTypeEntry::SmartPointerTypeEntry(SmartPointerTypeEntryPrivate *d) : + ComplexTypeEntry(d) +{ +} + +bool SmartPointerTypeEntry::matchesInstantiation(const TypeEntryCPtr &e) const +{ + S_D(const SmartPointerTypeEntry); + // No instantiations specified, or match + return d->m_instantiations.isEmpty() || d->instantiationIndex(e) != -1; +} + +static QString fixSmartPointerName(QString name) +{ + name.replace(u"::"_s, u"_"_s); + name.replace(u'<', u'_'); + name.remove(u'>'); + name.remove(u' '); + return name; +} + +QString SmartPointerTypeEntry::getTargetName(const AbstractMetaType &metaType) const +{ + S_D(const SmartPointerTypeEntry); + auto instantiatedTe = metaType.instantiations().constFirst().typeEntry(); + const auto index = d->instantiationIndex(instantiatedTe); + if (index != -1 && !d->m_instantiations.at(index).name.isEmpty()) + return d->m_instantiations.at(index).name; + + QString name = metaType.cppSignature(); + const auto templatePos = name.indexOf(u'<'); + if (templatePos != -1) { // "std::shared_ptr<A::B>" -> "shared_ptr<A::B>" + const auto colonPos = name.lastIndexOf(u"::"_s, templatePos); + if (colonPos != -1) + name.remove(0, colonPos + 2); + } + return fixSmartPointerName(name); +} + +// ----------------- NamespaceTypeEntry +class NamespaceTypeEntryPrivate : public ComplexTypeEntryPrivate +{ +public: + using ComplexTypeEntryPrivate::ComplexTypeEntryPrivate; + + QRegularExpression m_filePattern; + NamespaceTypeEntryCPtr m_extends; + TypeSystem::Visibility m_visibility = TypeSystem::Visibility::Auto; + bool m_hasPattern = false; + bool m_inlineNamespace = false; + bool m_generateUsing = true; // Whether to generate "using namespace" into wrapper +}; + +NamespaceTypeEntry::NamespaceTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntry(new NamespaceTypeEntryPrivate(entryName, NamespaceType, vr, parent)) +{ +} + +TypeEntry *NamespaceTypeEntry::clone() const +{ + S_D(const NamespaceTypeEntry); + return new NamespaceTypeEntry(new NamespaceTypeEntryPrivate(*d)); +} + +NamespaceTypeEntryCPtr NamespaceTypeEntry::extends() const +{ + S_D(const NamespaceTypeEntry); + return d->m_extends; +} + +void NamespaceTypeEntry::setExtends(const NamespaceTypeEntryCPtr &e) +{ + S_D(NamespaceTypeEntry); + d->m_extends = e; +} + +const QRegularExpression &NamespaceTypeEntry::filePattern() const +{ + S_D(const NamespaceTypeEntry); + return d->m_filePattern; +} + +void NamespaceTypeEntry::setFilePattern(const QRegularExpression &r) +{ + S_D(NamespaceTypeEntry); + d->m_filePattern = r; + d->m_hasPattern = !d->m_filePattern.pattern().isEmpty(); + if (d->m_hasPattern) + d->m_filePattern.optimize(); +} + +bool NamespaceTypeEntry::hasPattern() const +{ + S_D(const NamespaceTypeEntry); + return d->m_hasPattern; +} + +NamespaceTypeEntry::NamespaceTypeEntry(NamespaceTypeEntryPrivate *d) : + ComplexTypeEntry(d) +{ +} + +bool NamespaceTypeEntry::matchesFile(const QString &needle) const +{ + S_D(const NamespaceTypeEntry); + return d->m_filePattern.match(needle).hasMatch(); +} + +bool NamespaceTypeEntry::isVisible() const +{ + S_D(const NamespaceTypeEntry); + return d->m_visibility == TypeSystem::Visibility::Visible + || (d->m_visibility == TypeSystem::Visibility::Auto && !d->m_inlineNamespace); +} + +void NamespaceTypeEntry::setVisibility(TypeSystem::Visibility v) +{ + S_D(NamespaceTypeEntry); + d->m_visibility = v; +} + +bool NamespaceTypeEntry::isInlineNamespace() const +{ + S_D(const NamespaceTypeEntry); + return d->m_inlineNamespace; +} + +void NamespaceTypeEntry::setInlineNamespace(bool i) +{ + S_D(NamespaceTypeEntry); + d->m_inlineNamespace = i; +} + +bool NamespaceTypeEntry::isVisibleScope(const TypeEntryCPtr &e) +{ + return isVisibleScope(e.get()); +} + +bool NamespaceTypeEntry::isVisibleScope(const TypeEntry *e) +{ + return e->type() != TypeEntry::NamespaceType + || static_cast<const NamespaceTypeEntry *>(e)->isVisible(); +} + +bool NamespaceTypeEntry::generateUsing() const +{ + S_D(const NamespaceTypeEntry); + return d->m_generateUsing; +} + +void NamespaceTypeEntry::setGenerateUsing(bool generateUsing) +{ + S_D(NamespaceTypeEntry); + d->m_generateUsing = generateUsing; +} + +// ----------------- ValueTypeEntry + +class ValueTypeEntryPrivate : public ComplexTypeEntryPrivate +{ +public: + using ComplexTypeEntryPrivate::ComplexTypeEntryPrivate; + + QString m_targetConversionRule; + CustomConversionPtr m_customConversion; +}; + +ValueTypeEntry::ValueTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntry(new ValueTypeEntryPrivate(entryName, BasicValueType, vr, parent)) +{ +} + +bool ValueTypeEntry::hasCustomConversion() const +{ + S_D(const ValueTypeEntry); + return bool(d->m_customConversion); +} + +void ValueTypeEntry::setCustomConversion(const CustomConversionPtr &customConversion) +{ + S_D(ValueTypeEntry); + d->m_customConversion = customConversion; +} + +CustomConversionPtr ValueTypeEntry::customConversion() const +{ + S_D(const ValueTypeEntry); + return d->m_customConversion; +} + +void ValueTypeEntry::setTargetConversionRule(const QString &conversionRule) +{ + S_D(ValueTypeEntry); + d->m_targetConversionRule = conversionRule; +} + +QString ValueTypeEntry::targetConversionRule() const +{ + S_D(const ValueTypeEntry); + return d->m_targetConversionRule; +} + +bool ValueTypeEntry::hasTargetConversionRule() const +{ + S_D(const ValueTypeEntry); + return !d->m_targetConversionRule.isEmpty(); +} + +bool ValueTypeEntry::isValue() const +{ + return true; +} + +TypeEntry *ValueTypeEntry::clone() const +{ + S_D(const ValueTypeEntry); + return new ValueTypeEntry(new ValueTypeEntryPrivate(*d)); +} + +ValueTypeEntry::ValueTypeEntry(ComplexTypeEntryPrivate *d) : + ComplexTypeEntry(d) +{ +} + +ValueTypeEntry::ValueTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + ComplexTypeEntry(entryName, t, vr, parent) +{ +} + +// ----------------- FunctionTypeEntry +class FunctionTypeEntryPrivate : public TypeEntryPrivate +{ +public: + FunctionTypeEntryPrivate(const QString &entryName, const QString &signature, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntryPrivate(entryName, TypeEntry::FunctionType, vr, parent), + m_signatures(signature) + { + } + + QStringList m_signatures; + QString m_docFile; +}; + +FunctionTypeEntry::FunctionTypeEntry(const QString &entryName, const QString &signature, + const QVersionNumber &vr, + const TypeEntryCPtr &parent) : + TypeEntry(new FunctionTypeEntryPrivate(entryName, signature, vr, parent)) +{ +} + +void FunctionTypeEntry::addSignature(const QString &signature) +{ + S_D(FunctionTypeEntry); + d->m_signatures << signature; +} + +const QStringList &FunctionTypeEntry::signatures() const +{ + S_D(const FunctionTypeEntry); + return d->m_signatures; +} + +bool FunctionTypeEntry::hasSignature(const QString &signature) const +{ + S_D(const FunctionTypeEntry); + return d->m_signatures.contains(signature); +} + +QString FunctionTypeEntry::docFile() const +{ + S_D(const FunctionTypeEntry); + return d->m_docFile; +} + +void FunctionTypeEntry::setDocFile(const QString &df) +{ + S_D(FunctionTypeEntry); + d->m_docFile = df; +} + +TypeEntry *FunctionTypeEntry::clone() const +{ + S_D(const FunctionTypeEntry); + return new FunctionTypeEntry(new FunctionTypeEntryPrivate(*d)); +} + +FunctionTypeEntry::FunctionTypeEntry(FunctionTypeEntryPrivate *d) : + TypeEntry(d) +{ +} + +// ----------------- ObjectTypeEntry +ObjectTypeEntry::ObjectTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent) + : ComplexTypeEntry(entryName, ObjectType, vr, parent) +{ +} + +TypeEntry *ObjectTypeEntry::clone() const +{ + S_D(const ComplexTypeEntry); + return new ObjectTypeEntry(new ComplexTypeEntryPrivate(*d)); +} + +ObjectTypeEntry::ObjectTypeEntry(ComplexTypeEntryPrivate *d) : + ComplexTypeEntry(d) +{ +} + +#ifndef QT_NO_DEBUG_STREAM + +#define FORMAT_BOOL(name, var) \ + if (var) \ + debug << ", [" << name << ']'; + +#define FORMAT_NONEMPTY_STRING(name, var) \ + if (!var.isEmpty()) \ + debug << ", " << name << "=\"" << var << '"'; + +#define FORMAT_LIST_SIZE(name, var) \ + if (!var.isEmpty()) \ + debug << ", " << var.size() << ' ' << name; + +void TypeEntry::formatDebug(QDebug &debug) const +{ + const QString cppName = qualifiedCppName(); + debug << '"' << m_d->m_name << '"'; + if (m_d->m_name != cppName) + debug << "\", cppName=\"" << cppName << '"'; + debug << ", type=" << m_d->m_type << ", codeGeneration=" + << m_d->m_codeGeneration; + const QString &targetName = targetLangName(); + if (m_d->m_name != targetName) + debug << ", target=\"" << targetLangName() << '"'; + FORMAT_NONEMPTY_STRING("package", m_d->m_targetLangPackage) + FORMAT_BOOL("stream", m_d->m_stream) + FORMAT_BOOL("built-in", m_d->m_builtin) + if (m_d->m_viewOn) + debug << ", views=" << m_d->m_viewOn->name(); + if (!m_d->m_version.isNull() && m_d->m_version > QVersionNumber(0, 0)) + debug << ", version=" << m_d->m_version; + if (m_d->m_revision) + debug << ", revision=" << m_d->m_revision; + if (m_d->m_sbkIndex) + debug << ", sbkIndex=" << m_d->m_sbkIndex; + if (m_d->m_include.isValid()) + debug << ", include=" << m_d->m_include; + if (m_d->m_private) + debug << ", [private]"; + formatList(debug, "extraIncludes", m_d->m_extraIncludes, ", "); +} + +void PrimitiveTypeEntry::formatDebug(QDebug &debug) const +{ + TypeEntry::formatDebug(debug); + if (auto e = referencedTypeEntry()) { + debug << ", references"; + for (; e ; e = e->referencedTypeEntry()) + debug << ":\"" << e->qualifiedCppName() <<'"'; + } +} + +void ComplexTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const ComplexTypeEntry); + + TypeEntry::formatDebug(debug); + FORMAT_BOOL("polymorphicBase", d->m_polymorphicBase) + FORMAT_BOOL("genericClass", d->m_genericClass) + FORMAT_BOOL("deleteInMainThread", d->m_deleteInMainThread) + if (d->m_typeFlags != 0) + debug << ", typeFlags=" << d->m_typeFlags; + debug << ", copyableFlag=" << d->m_copyableFlag + << ", except=" << int(d->m_exceptionHandling) + << ", snakeCase=" << int(d->m_snakeCase); + FORMAT_NONEMPTY_STRING("defaultSuperclass", d->m_defaultSuperclass) + FORMAT_NONEMPTY_STRING("polymorphicIdValue", d->m_polymorphicIdValue) + FORMAT_NONEMPTY_STRING("targetType", d->m_targetType) + FORMAT_NONEMPTY_STRING("hash", d->m_hashFunction) + FORMAT_LIST_SIZE("addedFunctions", d->m_addedFunctions) + formatList(debug, "functionMods", d->m_functionMods, ", "); + FORMAT_LIST_SIZE("codeSnips", d->m_codeSnips) + FORMAT_LIST_SIZE("fieldMods", d->m_fieldMods) +} + +void CustomTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const CustomTypeEntry); + TypeEntry::formatDebug(debug); + debug << ", checkFunction=" << d->m_checkFunction; +} + +void PythonTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const PythonTypeEntry); + + CustomTypeEntry::formatDebug(debug); + debug << ", type=" << int(d->m_cPythonType); +} + +void FunctionTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const FunctionTypeEntry); + + TypeEntry::formatDebug(debug); + debug << "signatures=" << d->m_signatures; +} + +void TypedefEntry::formatDebug(QDebug &debug) const +{ + S_D(const TypedefEntry); + + ComplexTypeEntry::formatDebug(debug); + debug << ", sourceType=\"" << d->m_sourceType << '"' + << ", source=" << d->m_source << ", target=" << d->m_target; +} + +void EnumTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const EnumTypeEntry); + + TypeEntry::formatDebug(debug); + if (d->m_pythonEnumType != TypeSystem::PythonEnumType::Unspecified) + debug << ", python-type=" << int(d->m_pythonEnumType); + if (d->m_flags) + debug << ", flags=(" << d->m_flags << ')'; +} + +void NamespaceTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const NamespaceTypeEntry); + + ComplexTypeEntry::formatDebug(debug); + auto pattern = d->m_filePattern.pattern(); + FORMAT_NONEMPTY_STRING("pattern", pattern) + debug << ",visibility=" << d->m_visibility; + if (d->m_inlineNamespace) + debug << "[inline]"; +} + +QDebug operator<<(QDebug d, const OpaqueContainer &oc) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "OpaqueContainer(\"" << oc.name << "\": " << oc.templateParameters() << ')'; + return d; +} + +void ContainerTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const ContainerTypeEntry); + + ComplexTypeEntry::formatDebug(debug); + debug << ", type=" << d->m_containerKind << '"'; + if (!d->m_opaqueContainers.isEmpty()) + debug << ", opaque-containers=[" << d->m_opaqueContainers << ']'; +} + +void SmartPointerTypeEntry::formatDebug(QDebug &debug) const +{ + S_D(const SmartPointerTypeEntry); + + ComplexTypeEntry::formatDebug(debug); + if (!d->m_instantiations.isEmpty()) { + debug << "type=" << d->m_type << ", instantiations[" + << d->m_instantiations.size() << "]=("; + for (const auto &i : d->m_instantiations) { + debug << i.typeEntry->name() << ','; + if (!i.name.isEmpty()) + debug << "=\"" << i.name << '"'; + } + debug << ')'; + } +} + +QDebug operator<<(QDebug d, const TypeEntry *te) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "TypeEntry("; + if (te) + te->formatDebug(d); + else + d << '0'; + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const TypeEntryCPtr &te) +{ + d << te.get(); + return d; +} + +QDebug operator<<(QDebug d, const TemplateEntry *te) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << "TemplateEntry("; + if (te) { + d << '"' << te->name() << '"'; + } else { + d << '0'; + } + d << ')'; + return d; +} + +QDebug operator<<(QDebug d, const TemplateEntryCPtr &te) +{ + d << te.get(); + return d; +} +#endif // QT_NO_DEBUG_STREAM diff --git a/sources/shiboken6/ApiExtractor/typesystem.h b/sources/shiboken6/ApiExtractor/typesystem.h new file mode 100644 index 000000000..a2e4debc8 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystem.h @@ -0,0 +1,215 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPESYSTEM_H +#define TYPESYSTEM_H + +#include "include.h" +#include "typesystem_typedefs.h" + +#include <QtCore/qobjectdefs.h> +#include <QtCore/QString> +#include <QtCore/QScopedPointer> + +class AbstractMetaType; +class CustomTypeEntry; +class PrimitiveTypeEntry; +class SourceLocation; +class TypeSystemTypeEntry; + +class TypeEntryPrivate; + +QT_BEGIN_NAMESPACE +class QDebug; +class QVersionNumber; +QT_END_NAMESPACE + +class TypeEntry +{ + Q_GADGET +public: + Q_DISABLE_COPY_MOVE(TypeEntry) + + enum Type { + PrimitiveType, + VoidType, + VarargsType, + FlagsType, + EnumType, + EnumValue, + ConstantValueType, + TemplateArgumentType, + BasicValueType, + ContainerType, + ObjectType, + NamespaceType, + ArrayType, + TypeSystemType, + CustomType, + PythonType, + FunctionType, + SmartPointerType, + TypedefType + }; + Q_ENUM(Type) + + enum CodeGeneration { + GenerateNothing, // Rejection, private type, ConstantValueTypeEntry or similar + GenerationDisabled, // generate='no' in type system + GenerateCode, // Generate code + GenerateForSubclass, // Inherited from a loaded dependent type system. + }; + Q_ENUM(CodeGeneration) + + explicit TypeEntry(const QString &entryName, Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + virtual ~TypeEntry(); + + Type type() const; + + TypeEntryCPtr parent() const; + void setParent(const TypeEntryCPtr &); + bool isChildOf(const TypeEntryCPtr &p) const; + + bool isPrimitive() const; + bool isEnum() const; + bool isFlags() const; + bool isObject() const; + bool isNamespace() const; + bool isContainer() const; + bool isSmartPointer() const; + bool isUniquePointer() const; + bool isArray() const; + bool isTemplateArgument() const; + bool isVoid() const; + bool isVarargs() const; + bool isCustom() const; + bool isTypeSystem() const; + bool isFunction() const; + bool isEnumValue() const; + + bool stream() const; + void setStream(bool b); + + bool isBuiltIn() const; + void setBuiltIn(bool b); + + bool isPrivate() const; + void setPrivate(bool b); + + // The type's name in C++, fully qualified + QString name() const; + // C++ excluding inline namespaces + QString shortName() const; + // Name as specified in XML + QString entryName() const; + + CodeGeneration codeGeneration() const; + void setCodeGeneration(CodeGeneration cg); + + // Returns true if code must be generated for this entry, + // it will return false in case of types coming from typesystems + // included for reference only. + // NOTE: 'GenerateForSubclass' means 'generate="no"' + // on 'load-typesystem' tag + bool generateCode() const; + + /// Returns whether the C++ generators should generate this entry + bool shouldGenerate() const; + + int revision() const; + void setRevision(int r); // see typedatabase.cpp + int sbkIndex() const; // see typedatabase.cpp + void setSbkIndex(int i); + + virtual QString qualifiedCppName() const; + + /// Its type's name in target language API. + /// The target language API name represents how this type is referred on + /// low level code for the target language. Examples: for Java this would + /// be a JNI name, for Python it should represent the CPython type name. + /// \return string representing the target language API name + /// Currently used only for PrimitiveTypeEntry (attribute "target"). + CustomTypeEntryCPtr targetLangApiType() const; + bool hasTargetLangApiType() const; + void setTargetLangApiType(const CustomTypeEntryPtr &cte); + QString targetLangApiName() const; + + // The type's name in TargetLang + QString targetLangName() const; // "Foo.Bar" + void setTargetLangName(const QString &n); + QString targetLangEntryName() const; // "Bar" + + // The package + QString targetLangPackage() const; + void setTargetLangPackage(const QString &p); + + QString qualifiedTargetLangName() const; + + virtual bool isValue() const; + virtual bool isComplex() const; + + const IncludeList &extraIncludes() const; + void setExtraIncludes(const IncludeList &includes); + void addExtraInclude(const Include &newInclude); + + Include include() const; + void setInclude(const Include &inc); + + QVersionNumber version() const; + + // View on: Type to use for function argument conversion, fex + // std::string_view -> std::string for foo(std::string_view). + // cf AbstractMetaType::viewOn() + TypeEntryPtr viewOn() const; + void setViewOn(const TypeEntryPtr &v); + + virtual TypeEntry *clone() const; + + void useAsTypedef(const TypeEntryCPtr &source); + + SourceLocation sourceLocation() const; + void setSourceLocation(const SourceLocation &sourceLocation); + + // Query functions for generators + /// Returns true if the type passed has a Python wrapper for it. + /// Although namespace has a Python wrapper, it's not considered a type. + bool isWrapperType() const; + +#ifndef QT_NO_DEBUG_STREAM + virtual void formatDebug(QDebug &d) const; +#endif + +protected: + explicit TypeEntry(TypeEntryPrivate *d); + + const TypeEntryPrivate *d_func() const; + TypeEntryPrivate *d_func(); + + virtual QString buildTargetLangName() const; + +private: + bool setRevisionHelper(int r); + int sbkIndexHelper() const; + QScopedPointer<TypeEntryPrivate> m_d; +}; + +TypeSystemTypeEntryCPtr typeSystemTypeEntry(TypeEntryCPtr e); + +// cf AbstractMetaClass::targetLangEnclosingClass() +TypeEntryCPtr targetLangEnclosingEntry(const TypeEntryCPtr &e); + +bool isCppPrimitive(const TypeEntryCPtr &e); + +/// Returns true if the type is a primitive but not a C++ primitive. +bool isUserPrimitive(const TypeEntryCPtr &e); + +/// Returns true if the type is a C++ integral primitive, +/// i.e. bool, char, int, long, and their unsigned counterparts. +bool isCppIntegralPrimitive(const TypeEntryCPtr &e); + +/// Returns true if the type is an extended C++ primitive, a void*, +/// a const char*, or a std::string (cf isCppPrimitive()). +bool isExtendedCppPrimitive(const TypeEntryCPtr &e); + +#endif // TYPESYSTEM_H diff --git a/sources/shiboken6/ApiExtractor/typesystem_enums.h b/sources/shiboken6/ApiExtractor/typesystem_enums.h new file mode 100644 index 000000000..9ecbb08a1 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystem_enums.h @@ -0,0 +1,113 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPESYSTEM_ENUMS_H +#define TYPESYSTEM_ENUMS_H + +namespace TypeSystem +{ +enum Language { + TargetLangCode = 0x0001, + NativeCode = 0x0002, + ShellCode = 0x0004, + + // masks + All = TargetLangCode | NativeCode | ShellCode, + + TargetLangAndNativeCode = TargetLangCode | NativeCode +}; + +enum class AllowThread { + Unspecified, + Allow, + Disallow, + Auto +}; + +enum Ownership { + UnspecifiedOwnership, + DefaultOwnership, + TargetLangOwnership, + CppOwnership +}; + +enum CodeSnipPosition { + CodeSnipPositionBeginning, + CodeSnipPositionEnd, + CodeSnipPositionDeclaration, + CodeSnipPositionPyOverride, + CodeSnipPositionAny +}; + +enum DocModificationMode { + DocModificationAppend, + DocModificationPrepend, + DocModificationReplace, + DocModificationXPathReplace +}; + +enum class ExceptionHandling { + Unspecified, + Off, + AutoDefaultToOff, + AutoDefaultToOn, + On +}; + +enum class SnakeCase { + Unspecified, + Disabled, + Enabled, + Both +}; + +enum Visibility { // For namespaces + Unspecified, + Visible, + Invisible, + Auto +}; + +enum class BoolCast { // Generate nb_bool (overriding command line) + Unspecified, + Disabled, + Enabled +}; + +enum class CPythonType +{ + Bool, + Float, + Integer, + String, + Other +}; + +enum class QtMetaTypeRegistration +{ + Unspecified, + Enabled, + BaseEnabled, // Registration only for the base class of a hierarchy + Disabled +}; + +enum class SmartPointerType { + Shared, + Unique, + Handle, + ValueHandle +}; + +enum class PythonEnumType { + Unspecified, + Enum, + IntEnum, + Flag, + IntFlag +}; + +enum : int { OverloadNumberUnset = -1, OverloadNumberDefault = 99999 }; + +} // namespace TypeSystem + +#endif // TYPESYSTEM_ENUMS_H diff --git a/sources/shiboken6/ApiExtractor/typesystem_typedefs.h b/sources/shiboken6/ApiExtractor/typesystem_typedefs.h new file mode 100644 index 000000000..5a4e12ff2 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystem_typedefs.h @@ -0,0 +1,79 @@ +// Copyright (C) 2017 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPESYSTEM_TYPEDEFS_H +#define TYPESYSTEM_TYPEDEFS_H + +#include <QtCore/QList> + +#include <memory> + +class ArrayTypeEntry; +class ComplexTypeEntry; +class ConfigurableTypeEntry; +class ConstantValueTypeEntry; +class ContainerTypeEntry; +class CustomTypeEntry; +class EnumTypeEntry; +class EnumValueTypeEntry; +class FlagsTypeEntry; +class FunctionTypeEntry; +class NamespaceTypeEntry; +class ObjectTypeEntry; +class PrimitiveTypeEntry; +class SmartPointerTypeEntry; +class TemplateEntry; +class TypeEntry; +class TypedefEntry; +class TypeSystemTypeEntry; +class ValueTypeEntry; + +using ArrayTypeEntryPtr = std::shared_ptr<ArrayTypeEntry>; +using ComplexTypeEntryPtr = std::shared_ptr<ComplexTypeEntry>; +using ConfigurableTypeEntryPtr = std::shared_ptr<ConfigurableTypeEntry>; +using ConstantValueTypeEntryPtr = std::shared_ptr<ConstantValueTypeEntry>; +using ContainerTypeEntryPtr = std::shared_ptr<ContainerTypeEntry>; +using CustomTypeEntryPtr = std::shared_ptr<CustomTypeEntry>; +using EnumTypeEntryPtr = std::shared_ptr<EnumTypeEntry>; +using EnumValueTypeEntryPtr = std::shared_ptr<EnumValueTypeEntry>; +using FlagsTypeEntryPtr = std::shared_ptr<FlagsTypeEntry>; +using FunctionTypeEntryPtr = std::shared_ptr<FunctionTypeEntry>; +using NamespaceTypeEntryPtr = std::shared_ptr<NamespaceTypeEntry>; +using ObjectTypeEntryPtr = std::shared_ptr<ObjectTypeEntry>; +using PrimitiveTypeEntryPtr = std::shared_ptr<PrimitiveTypeEntry>; +using SmartPointerTypeEntryPtr = std::shared_ptr<SmartPointerTypeEntry>; +using TemplateEntryPtr = std::shared_ptr<TemplateEntry>; +using TypeEntryPtr = std::shared_ptr<TypeEntry>; +using TypedefEntryPtr = std::shared_ptr<TypedefEntry>; +using TypeSystemTypeEntryPtr = std::shared_ptr<TypeSystemTypeEntry>; +using ValueTypeEntryPtr = std::shared_ptr<ValueTypeEntry>; + +using ArrayTypeEntryCPtr = std::shared_ptr<const ArrayTypeEntry>; +using ComplexTypeEntryCPtr = std::shared_ptr<const ComplexTypeEntry>; +using ConstantValueTypeEntryCPtr = std::shared_ptr<const ConstantValueTypeEntry>; +using ConfigurableTypeEntryCPtr = std::shared_ptr<const ConfigurableTypeEntry>; +using ContainerTypeEntryCPtr = std::shared_ptr<const ContainerTypeEntry>; +using CustomTypeEntryCPtr = std::shared_ptr<const CustomTypeEntry>; +using EnumTypeEntryCPtr = std::shared_ptr<const EnumTypeEntry>; +using EnumValueTypeEntryCPtr = std::shared_ptr<const EnumValueTypeEntry>; +using FlagsTypeEntryCPtr = std::shared_ptr<const FlagsTypeEntry>; +using FunctionTypeEntryCPtr = std::shared_ptr<const FunctionTypeEntry>; +using NamespaceTypeEntryCPtr = std::shared_ptr<const NamespaceTypeEntry>; +using ObjectTypeEntryCPtr = std::shared_ptr<const ObjectTypeEntry>; +using PrimitiveTypeEntryCPtr = std::shared_ptr<const PrimitiveTypeEntry>; +using SmartPointerTypeEntryCPtr = std::shared_ptr<const SmartPointerTypeEntry>; +using TemplateEntryCPtr = std::shared_ptr<const TemplateEntry>; +using TypeEntryCPtr = std::shared_ptr<const TypeEntry>; +using TypedefEntryCPtr = std::shared_ptr<const TypedefEntry>; +using TypeSystemTypeEntryCPtr = std::shared_ptr<const TypeSystemTypeEntry>; +using ValueTypeEntryCPtr = std::shared_ptr<const ValueTypeEntry>; + +using ComplexTypeEntryCList = QList<ComplexTypeEntryCPtr>; +using ContainerTypeEntryCList = QList<ContainerTypeEntryCPtr>; +using NamespaceTypeEntryList = QList<NamespaceTypeEntryPtr>; +using PrimitiveTypeEntryCList = QList<PrimitiveTypeEntryCPtr>; +using SmartPointerTypeEntryList = QList<SmartPointerTypeEntryCPtr>; +using TypeEntryList = QList<TypeEntryPtr>; +using TypeEntryCList = QList<TypeEntryCPtr>; + +#endif // TYPESYSTEM_TYPEDEFS_H diff --git a/sources/shiboken6/ApiExtractor/typesystemparser.cpp b/sources/shiboken6/ApiExtractor/typesystemparser.cpp new file mode 100644 index 000000000..2b686e997 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystemparser.cpp @@ -0,0 +1,3648 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "typesystemparser_p.h" +#include "anystringview_helpers.h" +#include "addedfunction.h" +#include "codesnip.h" +#include "enumtypeentry.h" +#include "containertypeentry.h" +#include "customconversion.h" +#include "customtypenentry.h" +#include "flagstypeentry.h" +#include "functiontypeentry.h" +#include "namespacetypeentry.h" +#include "objecttypeentry.h" +#include "primitivetypeentry.h" +#include "smartpointertypeentry.h" +#include "typedefentry.h" +#include "typesystemtypeentry.h" +#include "valuetypeentry.h" +#include "modifications.h" +#include "typedatabase.h" +#include "messages.h" +#include "reporthandler.h" +#include "sourcelocation.h" +#include "conditionalstreamreader.h" + +#include "qtcompat.h" + +#include <QtCore/QDebug> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QFileInfo> +#include <QtCore/QRegularExpression> +#include <QtCore/QSet> +#include <QtCore/QStringView> +#include <QtCore/QStringAlgorithms> +#include <QtCore/QVersionNumber> +#include <QtCore/QXmlStreamAttributes> +#include <QtCore/QXmlStreamReader> +#include <QtCore/QXmlStreamEntityResolver> + +#include <algorithm> +#include <optional> +#include <memory> + +using namespace Qt::StringLiterals; + +constexpr auto allowThreadAttribute = "allow-thread"_L1; +constexpr auto checkFunctionAttribute = "check-function"_L1; +constexpr auto copyableAttribute = "copyable"_L1; +constexpr auto accessAttribute = "access"_L1; +constexpr auto actionAttribute = "action"_L1; +constexpr auto quoteAfterLineAttribute = "quote-after-line"_L1; +constexpr auto quoteBeforeLineAttribute = "quote-before-line"_L1; +constexpr auto textAttribute = "text"_L1; +constexpr auto nameAttribute = "name"_L1; +constexpr auto sinceAttribute = "since"_L1; +constexpr auto untilAttribute = "until"_L1; +constexpr auto defaultSuperclassAttribute = "default-superclass"_L1; +constexpr auto deleteInMainThreadAttribute = "delete-in-main-thread"_L1; +constexpr auto deprecatedAttribute = "deprecated"_L1; +constexpr auto disableWrapperAttribute = "disable-wrapper"_L1; +constexpr auto docFileAttribute = "doc-file"_L1; +constexpr auto exceptionHandlingAttribute = "exception-handling"_L1; +constexpr auto extensibleAttribute = "extensible"_L1; +constexpr auto fileNameAttribute = "file-name"_L1; +constexpr auto fileAttribute = "file"_L1; +constexpr auto flagsAttribute = "flags"_L1; +constexpr auto forceAbstractAttribute = "force-abstract"_L1; +constexpr auto forceIntegerAttribute = "force-integer"_L1; +constexpr auto formatAttribute = "format"_L1; +constexpr auto generateUsingAttribute = "generate-using"_L1; +constexpr auto generateFunctionsAttribute = "generate-functions"_L1; +constexpr auto classAttribute = "class"_L1; +constexpr auto generateAttribute = "generate"_L1; +constexpr auto generateGetSetDefAttribute = "generate-getsetdef"_L1; +constexpr auto genericClassAttribute = "generic-class"_L1; +constexpr auto indexAttribute = "index"_L1; +constexpr auto invalidateAfterUseAttribute = "invalidate-after-use"_L1; +constexpr auto isNullAttribute = "isNull"_L1; +constexpr auto locationAttribute = "location"_L1; +constexpr auto modifiedTypeAttribute = "modified-type"_L1; +constexpr auto opaqueContainerAttribute = "opaque-containers"_L1; +constexpr auto operatorBoolAttribute = "operator-bool"_L1; +constexpr auto parentManagementAttribute = "parent-management"_L1; +constexpr auto pyiTypeAttribute = "pyi-type"_L1; +constexpr auto overloadNumberAttribute = "overload-number"_L1; +constexpr auto ownershipAttribute = "owner"_L1; +constexpr auto packageAttribute = "package"_L1; +constexpr auto polymorphicBaseAttribute = "polymorphic-base"_L1; +constexpr auto positionAttribute = "position"_L1; +constexpr auto preferredConversionAttribute = "preferred-conversion"_L1; +constexpr auto preferredTargetLangTypeAttribute = "preferred-target-lang-type"_L1; +constexpr auto pythonEnumTypeAttribute = "python-type"_L1; +constexpr auto pythonOverrideAttribute = "python-override"_L1; +constexpr auto cppEnumTypeAttribute = "cpp-type"_L1; +constexpr auto qtMetaObjectFunctionsAttribute = "qt-metaobject"_L1; +constexpr auto qtMetaTypeAttribute = "qt-register-metatype"_L1; +constexpr auto removeAttribute = "remove"_L1; +constexpr auto renameAttribute = "rename"_L1; +constexpr auto readAttribute = "read"_L1; +constexpr auto targetLangNameAttribute = "target-lang-name"_L1; +constexpr auto writeAttribute = "write"_L1; +constexpr auto opaqueContainerFieldAttribute = "opaque-container"_L1; +constexpr auto replaceAttribute = "replace"_L1; +constexpr auto toAttribute = "to"_L1; +constexpr auto signatureAttribute = "signature"_L1; +constexpr auto snippetAttribute = "snippet"_L1; +constexpr auto snakeCaseAttribute = "snake-case"_L1; +constexpr auto staticAttribute = "static"_L1; +constexpr auto classmethodAttribute = "classmethod"_L1; +constexpr auto threadAttribute = "thread"_L1; +constexpr auto sourceAttribute = "source"_L1; +constexpr auto streamAttribute = "stream"_L1; +constexpr auto privateAttribute = "private"_L1; +constexpr auto xPathAttribute = "xpath"_L1; +constexpr auto virtualSlotAttribute = "virtual-slot"_L1; +constexpr auto visibleAttribute = "visible"_L1; +constexpr auto enumIdentifiedByValueAttribute = "identified-by-value"_L1; +constexpr auto subModuleOfAttribute = "submodule-of"_L1; + +constexpr auto noAttributeValue = "no"_L1; +constexpr auto yesAttributeValue = "yes"_L1; +constexpr auto trueAttributeValue = "true"_L1; +constexpr auto falseAttributeValue = "false"_L1; + +static bool isTypeEntry(StackElement el) +{ + return el >= StackElement::FirstTypeEntry && el <= StackElement::LastTypeEntry; +} + +static bool isComplexTypeEntry(StackElement el) +{ + return el >= StackElement::FirstTypeEntry && el <= StackElement::LastComplexTypeEntry; +} + +static bool isDocumentation(StackElement el) +{ + return el >= StackElement::FirstDocumentation && el <= StackElement::LastDocumentation; +} + +static QList<CustomConversionPtr> customConversionsForReview; + +// Set a regular expression for rejection from text. By legacy, those are fixed +// strings, except for '*' meaning 'match all'. Enclosing in "^..$" +// indicates regular expression. +static bool setRejectionRegularExpression(const QString &patternIn, + QRegularExpression *re, + QString *errorMessage) +{ + QString pattern; + if (patternIn.startsWith(u'^') && patternIn.endsWith(u'$')) + pattern = patternIn; + else if (patternIn == u"*") + pattern = "^.*$"_L1; + else + pattern = u'^' + QRegularExpression::escape(patternIn) + u'$'; + re->setPattern(pattern); + if (!re->isValid()) { + *errorMessage = msgInvalidRegularExpression(patternIn, re->errorString()); + return false; + } + return true; +} + +static inline bool hasFileSnippetAttributes(const QXmlStreamAttributes *attributes) +{ + return attributes->hasAttribute(fileAttribute); +} + +// Extract a snippet from a file within annotation "// @snippet label". +std::optional<QString> + extractSnippet(const QString &code, const QString &snippetLabel) +{ + if (snippetLabel.isEmpty()) + return code; + const QString pattern = R"(^\s*//\s*@snippet\s+)"_L1 + + QRegularExpression::escape(snippetLabel) + + R"(\s*$)"_L1; + const QRegularExpression snippetRe(pattern); + Q_ASSERT(snippetRe.isValid()); + + bool useLine = false; + bool foundLabel = false; + QString result; + const auto lines = QStringView{code}.split(u'\n'); + for (const auto &line : lines) { + if (snippetRe.matchView(line).hasMatch()) { + foundLabel = true; + useLine = !useLine; + if (!useLine) + break; // End of snippet reached + } else if (useLine) + result += line.toString() + u'\n'; + } + if (!foundLabel) + return {}; + return CodeSnipAbstract::fixSpaces(result); +} + +template <class EnumType> +struct EnumLookup +{ + QStringView name; + EnumType value; +}; + +// Helper macros to define lookup functions that take a QStringView needle +// and an optional default return value. +#define ENUM_LOOKUP_BEGIN(EnumType, caseSensitivity, functionName) \ +static std::optional<EnumType> functionName(QStringView needle) \ +{ \ + using HaystackEntry = EnumLookup<EnumType>; \ + constexpr auto cs = caseSensitivity; \ + static constexpr HaystackEntry haystack[] = + +#define ENUM_LOOKUP_LINEAR_SEARCH \ + auto pred = [cs, needle](const HaystackEntry &he) { \ + return he.name.compare(needle, cs) == 0; \ + }; \ + auto end = std::cend(haystack); \ + auto it = std::find_if(std::cbegin(haystack), end, pred); \ + if (it != end) \ + return it->value; \ + return std::nullopt; \ +} + +ENUM_LOOKUP_BEGIN(TypeSystem::AllowThread, Qt::CaseInsensitive, + allowThreadFromAttribute) + { + {u"yes", TypeSystem::AllowThread::Allow}, + {u"true", TypeSystem::AllowThread::Allow}, + {u"auto", TypeSystem::AllowThread::Auto}, + {u"no", TypeSystem::AllowThread::Disallow}, + {u"false", TypeSystem::AllowThread::Disallow}, + }; +ENUM_LOOKUP_LINEAR_SEARCH + + +ENUM_LOOKUP_BEGIN(TypeSystem::BoolCast, Qt::CaseInsensitive, + boolCastFromAttribute) + { + {u"yes", TypeSystem::BoolCast::Enabled}, + {u"true", TypeSystem::BoolCast::Enabled}, + {u"no", TypeSystem::BoolCast::Disabled}, + {u"false", TypeSystem::BoolCast::Disabled}, + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::PythonEnumType, Qt::CaseSensitive, + pythonEnumTypeFromAttribute) + { + {u"Enum", TypeSystem::PythonEnumType::Enum}, + {u"IntEnum", TypeSystem::PythonEnumType::IntEnum}, + {u"Flag", TypeSystem::PythonEnumType::Flag}, + {u"IntFlag", TypeSystem::PythonEnumType::IntFlag}, + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::QtMetaTypeRegistration, Qt::CaseSensitive, + qtMetaTypeFromAttribute) + { + {u"yes", TypeSystem::QtMetaTypeRegistration::Enabled}, + {u"true", TypeSystem::QtMetaTypeRegistration::Enabled}, + {u"base", TypeSystem::QtMetaTypeRegistration::BaseEnabled}, + {u"no", TypeSystem::QtMetaTypeRegistration::Disabled}, + {u"false", TypeSystem::QtMetaTypeRegistration::Disabled}, + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::Language, Qt::CaseInsensitive, + languageFromAttribute) + { + {u"all", TypeSystem::All}, // sorted! + {u"native", TypeSystem::NativeCode}, // em algum lugar do cpp + {u"shell", TypeSystem::ShellCode}, // coloca no header, mas antes da declaracao da classe + {u"target", TypeSystem::TargetLangCode} // em algum lugar do cpp + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::Ownership, Qt::CaseInsensitive, + ownershipFromFromAttribute) + { + {u"target", TypeSystem::TargetLangOwnership}, + {u"c++", TypeSystem::CppOwnership}, + {u"default", TypeSystem::DefaultOwnership} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(AddedFunction::Access, Qt::CaseInsensitive, + addedFunctionAccessFromAttribute) + { + {u"public", AddedFunction::Public}, + {u"protected", AddedFunction::Protected}, + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(FunctionModification::ModifierFlag, Qt::CaseSensitive, + modifierFromAttribute) + { + {u"private", FunctionModification::Private}, + {u"public", FunctionModification::Public}, + {u"protected", FunctionModification::Protected}, + {u"rename", FunctionModification::Rename}, + {u"final", FunctionModification::Final}, + {u"non-final", FunctionModification::NonFinal} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(ReferenceCount::Action, Qt::CaseInsensitive, + referenceCountFromAttribute) + { + {u"add", ReferenceCount::Add}, + {u"add-all", ReferenceCount::AddAll}, + {u"remove", ReferenceCount::Remove}, + {u"set", ReferenceCount::Set}, + {u"ignore", ReferenceCount::Ignore} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(ArgumentOwner::Action, Qt::CaseInsensitive, + argumentOwnerActionFromAttribute) + { + {u"add", ArgumentOwner::Add}, + {u"remove", ArgumentOwner::Remove} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::CodeSnipPosition, Qt::CaseInsensitive, + codeSnipPositionFromAttribute) + { + {u"beginning", TypeSystem::CodeSnipPositionBeginning}, + {u"end", TypeSystem::CodeSnipPositionEnd}, + {u"declaration", TypeSystem::CodeSnipPositionDeclaration}, + {u"override", TypeSystem::CodeSnipPositionPyOverride} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(Include::IncludeType, Qt::CaseInsensitive, + locationFromAttribute) + { + {u"global", Include::IncludePath}, + {u"local", Include::LocalPath}, + {u"target", Include::TargetLangImport} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::DocModificationMode, Qt::CaseInsensitive, + docModificationFromAttribute) + { + {u"append", TypeSystem::DocModificationAppend}, + {u"prepend", TypeSystem::DocModificationPrepend}, + {u"replace", TypeSystem::DocModificationReplace} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(ContainerTypeEntry::ContainerKind, Qt::CaseSensitive, + containerTypeFromAttribute) + { + {u"list", ContainerTypeEntry::ListContainer}, + {u"string-list", ContainerTypeEntry::ListContainer}, + {u"linked-list", ContainerTypeEntry::ListContainer}, + {u"vector", ContainerTypeEntry::ListContainer}, + {u"stack", ContainerTypeEntry::ListContainer}, + {u"queue", ContainerTypeEntry::ListContainer}, + {u"set", ContainerTypeEntry::SetContainer}, + {u"map", ContainerTypeEntry::MapContainer}, + {u"multi-map", ContainerTypeEntry::MultiMapContainer}, + {u"hash", ContainerTypeEntry::MapContainer}, + {u"multi-hash", ContainerTypeEntry::MultiMapContainer}, + {u"pair", ContainerTypeEntry::PairContainer}, + {u"span", ContainerTypeEntry::SpanContainer} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeRejection::MatchType, Qt::CaseSensitive, + typeRejectionFromAttribute) + { + {u"class", TypeRejection::ExcludeClass}, + {u"function-name", TypeRejection::Function}, + {u"field-name", TypeRejection::Field}, + {u"enum-name", TypeRejection::Enum }, + {u"argument-type", TypeRejection::ArgumentType}, + {u"return-type", TypeRejection::ReturnType} + }; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::ExceptionHandling, Qt::CaseSensitive, + exceptionHandlingFromAttribute) +{ + {u"no", TypeSystem::ExceptionHandling::Off}, + {u"false", TypeSystem::ExceptionHandling::Off}, + {u"auto-off", TypeSystem::ExceptionHandling::AutoDefaultToOff}, + {u"auto-on", TypeSystem::ExceptionHandling::AutoDefaultToOn}, + {u"yes", TypeSystem::ExceptionHandling::On}, + {u"true", TypeSystem::ExceptionHandling::On}, +}; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::SmartPointerType, Qt::CaseSensitive, + smartPointerTypeFromAttribute) +{ + {u"handle", TypeSystem::SmartPointerType::Handle}, + {u"unique", TypeSystem::SmartPointerType::Unique}, + {u"value-handle", TypeSystem::SmartPointerType::ValueHandle}, + {u"shared", TypeSystem::SmartPointerType::Shared} +}; +ENUM_LOOKUP_LINEAR_SEARCH + +template <class EnumType> +static std::optional<EnumType> + lookupHashElement(const QHash<QStringView, EnumType> &hash, + QStringView needle, Qt::CaseSensitivity cs = Qt::CaseSensitive) +{ + auto end = hash.cend(); + auto it = hash.constFind(needle); + if (it != end) + return it.value(); + if (cs == Qt::CaseInsensitive) { // brute force search for the unlikely case mismatch + for (it = hash.cbegin(); it != end; ++it) { + if (it.key().compare(needle, cs) == 0) + return it.value(); + } + } + return std::nullopt; +} + +using StackElementHash = QHash<QStringView, StackElement>; + +static const StackElementHash &stackElementHash() +{ + static const StackElementHash result{ + {u"add-conversion", StackElement::AddConversion}, + {u"add-function", StackElement::AddFunction}, + {u"add-pymethoddef", StackElement::AddPyMethodDef}, + {u"array", StackElement::Array}, + {u"configuration", StackElement::Configuration}, + {u"container-type", StackElement::ContainerTypeEntry}, + {u"conversion-rule", StackElement::ConversionRule}, + {u"custom-constructor", StackElement::Unimplemented}, + {u"custom-destructor", StackElement::Unimplemented}, + {u"custom-type", StackElement::CustomTypeEntry}, + {u"declare-function", StackElement::DeclareFunction}, + {u"define-ownership", StackElement::DefineOwnership}, + {u"enum-type", StackElement::EnumTypeEntry}, + {u"extra-includes", StackElement::ExtraIncludes}, + {u"function", StackElement::FunctionTypeEntry}, + {u"import-file", StackElement::ImportFile}, + {u"include", StackElement::Include}, + {u"inject-code", StackElement::InjectCode}, + {u"inject-documentation", StackElement::InjectDocumentation}, + {u"insert-template", StackElement::InsertTemplate}, + {u"interface-type", StackElement::InterfaceTypeEntry}, + {u"load-typesystem", StackElement::LoadTypesystem}, + {u"modify-argument", StackElement::ModifyArgument}, + {u"modify-documentation", StackElement::ModifyDocumentation}, + {u"modify-field", StackElement::ModifyField}, + {u"modify-function", StackElement::ModifyFunction}, + {u"namespace-type", StackElement::NamespaceTypeEntry}, + {u"native-to-target", StackElement::NativeToTarget}, + {u"no-null-pointer", StackElement::NoNullPointers}, + {u"object-type", StackElement::ObjectTypeEntry}, + {u"opaque-container", StackElement::OpaqueContainer}, + {u"parent", StackElement::ParentOwner}, + {u"primitive-type", StackElement::PrimitiveTypeEntry}, + {u"property", StackElement::Property}, + {u"reference-count", StackElement::ReferenceCount}, + {u"reject-enum-value", StackElement::RejectEnumValue}, + {u"rejection", StackElement::Rejection}, + {u"remove-argument", StackElement::RemoveArgument}, + {u"remove-default-expression", StackElement::RemoveDefaultExpression}, + {u"rename", StackElement::Rename}, // ### fixme PySide7: remove + {u"replace", StackElement::Replace}, + {u"replace-default-expression", StackElement::ReplaceDefaultExpression}, + {u"replace-type", StackElement::ReplaceType}, + {u"smart-pointer-type", StackElement::SmartPointerTypeEntry}, + {u"suppress-warning", StackElement::SuppressedWarning}, + {u"system-include", StackElement::SystemInclude}, + {u"target-to-native", StackElement::TargetToNative}, + {u"template", StackElement::Template}, + {u"typedef-type", StackElement::TypedefTypeEntry}, + {u"typesystem", StackElement::Root}, + {u"value-type", StackElement::ValueTypeEntry}, + }; + return result; +} + +static std::optional<StackElement> elementFromTag(QStringView needle) +{ + return lookupHashElement(stackElementHash(), needle, + Qt::CaseInsensitive); // FIXME PYSIDE-7: case sensitive +} + +static QStringView tagFromElement(StackElement st) +{ + return stackElementHash().key(st); +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, StackElement st) +{ + QDebugStateSaver saver(d); + d.noquote(); + d.nospace(); + d << tagFromElement(st); + return d; +} +#endif // QT_NO_DEBUG_STREAM + +ENUM_LOOKUP_BEGIN(TypeSystem::SnakeCase, Qt::CaseSensitive, + snakeCaseFromAttribute) +{ + {u"no", TypeSystem::SnakeCase::Disabled}, + {u"false", TypeSystem::SnakeCase::Disabled}, + {u"yes", TypeSystem::SnakeCase::Enabled}, + {u"true", TypeSystem::SnakeCase::Enabled}, + {u"both", TypeSystem::SnakeCase::Both}, +}; +ENUM_LOOKUP_LINEAR_SEARCH + +ENUM_LOOKUP_BEGIN(TypeSystem::Visibility, Qt::CaseSensitive, + visibilityFromAttribute) +{ + {u"no", TypeSystem::Visibility::Invisible}, + {u"false", TypeSystem::Visibility::Invisible}, + {u"auto", TypeSystem::Visibility::Auto}, + {u"yes", TypeSystem::Visibility::Visible}, + {u"true", TypeSystem::Visibility::Visible}, +}; +ENUM_LOOKUP_LINEAR_SEARCH + +static int indexOfAttribute(const QXmlStreamAttributes &atts, + QAnyStringView name) +{ + for (qsizetype i = 0, size = atts.size(); i < size; ++i) { + if (atts.at(i).qualifiedName() == name) + return i; + } + return -1; +} + +static QString msgMissingAttribute(const QString &a) +{ + return u"Required attribute '"_s + a + + u"' missing."_s; +} + +QTextStream &operator<<(QTextStream &str, const QXmlStreamAttribute &attribute) +{ + str << attribute.qualifiedName() << "=\"" << attribute.value() << '"'; + return str; +} + +static QString msgInvalidAttributeValue(const QXmlStreamAttribute &attribute) +{ + QString result; + QTextStream(&result) << "Invalid attribute value:" << attribute; + return result; +} + +static QString msgUnusedAttributes(QStringView tag, const QXmlStreamAttributes &attributes) +{ + QString result; + QTextStream str(&result); + str << attributes.size() << " attributes(s) unused on <" << tag << ">: "; + for (qsizetype i = 0, size = attributes.size(); i < size; ++i) { + if (i) + str << ", "; + str << attributes.at(i); + } + return result; +} + +// QXmlStreamEntityResolver::resolveEntity(publicId, systemId) is not +// implemented; resolve via undeclared entities instead. +class TypeSystemEntityResolver : public QXmlStreamEntityResolver +{ +public: + explicit TypeSystemEntityResolver(const QString ¤tPath) : + m_currentPath(currentPath) {} + + QString resolveUndeclaredEntity(const QString &name) override; + +private: + QString readFile(const QString &entityName, QString *errorMessage) const; + + const QString m_currentPath; +}; + +QString TypeSystemEntityResolver::readFile(const QString &entityName, QString *errorMessage) const +{ + QString fileName = entityName; + if (!fileName.contains(u'.')) + fileName += u".xml"_s; + QString path = TypeDatabase::instance()->modifiedTypesystemFilepath(fileName, m_currentPath); + if (!QFileInfo::exists(path)) // PySide6-specific hack + fileName.prepend(u"typesystem_"_s); + path = TypeDatabase::instance()->modifiedTypesystemFilepath(fileName, m_currentPath); + if (!QFileInfo::exists(path)) { + *errorMessage = u"Unable to resolve: "_s + entityName; + return {}; + } + QFile file(path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + *errorMessage = msgCannotOpenForReading(file); + return {}; + } + QString result = QString::fromUtf8(file.readAll()).trimmed(); + // Remove license header comments on which QXmlStreamReader chokes + if (result.startsWith(u"<!--")) { + const auto commentEnd = result.indexOf(u"-->"); + if (commentEnd != -1) { + result.remove(0, commentEnd + 3); + result = result.trimmed(); + } + } + return result; +} + +QString TypeSystemEntityResolver::resolveUndeclaredEntity(const QString &name) +{ + QString errorMessage; + const QString result = readFile(name, &errorMessage); + if (result.isEmpty()) { // The parser will fail and display the line number. + qCWarning(lcShiboken, "%s", + qPrintable(msgCannotResolveEntity(name, errorMessage))); + } + return result; +} + +// State depending on element stack +enum class ParserState +{ + None, + PrimitiveTypeNativeToTargetConversion, + PrimitiveTypeTargetToNativeConversion, + ArgumentConversion, // Argument conversion rule with class attribute + ArgumentNativeToTargetConversion, + ArgumentTargetToNativeConversion, + FunctionCodeInjection, + TypeEntryCodeInjection, + TypeSystemCodeInjection, + Template +}; + +TypeSystemParser::TypeSystemParser(const std::shared_ptr<TypeDatabaseParserContext> &context, + bool generate) : + m_context(context), + m_generate(generate ? TypeEntry::GenerateCode : TypeEntry::GenerateForSubclass) +{ +} + +TypeSystemParser::~TypeSystemParser() = default; + +static QString readerFileName(const ConditionalStreamReader &reader) +{ + const auto *file = qobject_cast<const QFile *>(reader.device()); + return file != nullptr ? file->fileName() : QString(); +} + +static QString msgReaderMessage(const ConditionalStreamReader &reader, + const char *type, + const QString &what) +{ + QString message; + QTextStream str(&message); + const QString fileName = readerFileName(reader); + if (fileName.isEmpty()) + str << "<stdin>:"; + else + str << QDir::toNativeSeparators(fileName) << ':'; + // Use a tab separator like SourceLocation for suppression detection + str << reader.lineNumber() << ':' << reader.columnNumber() + << ":\t" << type << ": " << what; + return message; +} + +static QString msgReaderWarning(const ConditionalStreamReader &reader, const QString &what) +{ + return msgReaderMessage(reader, "Warning", what); +} + +static QString msgReaderError(const ConditionalStreamReader &reader, const QString &what) +{ + return msgReaderMessage(reader, "Error", what); +} + +static QString msgUnimplementedElementWarning(const ConditionalStreamReader &reader, + QAnyStringView name) +{ + QString message; + QTextStream(&message) << "The element \"" << name + << "\" is not implemented."; + return msgReaderMessage(reader, "Warning", message); +} + +static QString msgUnimplementedAttributeWarning(const ConditionalStreamReader &reader, + QStringView name) +{ + QString message; + QTextStream(&message) << "The attribute \"" << name + << "\" is not implemented."; + return msgReaderMessage(reader, "Warning", message); +} + +static inline QString msgUnimplementedAttributeWarning(const ConditionalStreamReader &reader, + const QXmlStreamAttribute &attribute) +{ + return msgUnimplementedAttributeWarning(reader, attribute.qualifiedName()); +} + +static QString + msgUnimplementedAttributeValueWarning(const ConditionalStreamReader &reader, + QAnyStringView name, QAnyStringView value) +{ + QString message; + QTextStream(&message) << "The value \"" << value + << "\" of the attribute \"" << name << "\" is not implemented."; + return msgReaderMessage(reader, "Warning", message); +} + +static inline + QString msgUnimplementedAttributeValueWarning(const ConditionalStreamReader &reader, + const QXmlStreamAttribute &attribute) +{ + return msgUnimplementedAttributeValueWarning(reader, + attribute.qualifiedName(), + attribute.value()); +} + +static bool addRejection(TypeDatabase *database, bool generate, QXmlStreamAttributes *attributes, + QString *errorMessage) +{ + const auto classIndex = indexOfAttribute(*attributes, classAttribute); + if (classIndex == -1) { + *errorMessage = msgMissingAttribute(classAttribute); + return false; + } + + TypeRejection rejection; + rejection.generate = generate; + const QString className = attributes->takeAt(classIndex).value().toString(); + if (!setRejectionRegularExpression(className, &rejection.className, errorMessage)) + return false; + + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto &attribute = attributes->at(i); + const auto name = attribute.qualifiedName(); + const auto typeOpt = typeRejectionFromAttribute(name); + if (!typeOpt.has_value()) { + *errorMessage = msgInvalidAttributeValue(attribute); + return false; + } + switch (typeOpt.value()) { + case TypeRejection::Function: + case TypeRejection::Field: + case TypeRejection::Enum: + case TypeRejection::ArgumentType: + case TypeRejection::ReturnType: { + const QString pattern = attributes->takeAt(i).value().toString(); + if (!setRejectionRegularExpression(pattern, &rejection.pattern, errorMessage)) + return false; + rejection.matchType = typeOpt.value(); + database->addRejection(rejection); + return true; + } + case TypeRejection::ExcludeClass: + break; + } + } + + // Special case: When all fields except class are empty, completely exclude class + if (className == u"*") { + *errorMessage = u"bad reject entry, neither 'class', 'function-name'" + " nor 'field' specified"_s; + return false; + } + rejection.matchType = TypeRejection::ExcludeClass; + database->addRejection(rejection); + return true; +} + +bool TypeSystemParser::parse(ConditionalStreamReader &reader) +{ + m_error.clear(); + m_currentPath.clear(); + m_currentFile.clear(); + return parseXml(reader); +} + +bool TypeSystemParser::parseXml(ConditionalStreamReader &reader) +{ + const QString fileName = readerFileName(reader); + if (!fileName.isEmpty()) { + QFileInfo fi(fileName); + m_currentPath = fi.absolutePath(); + m_currentFile = fi.absoluteFilePath(); + } + m_entityResolver.reset(new TypeSystemEntityResolver(m_currentPath)); + reader.setEntityResolver(m_entityResolver.data()); + + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::NoToken: + case QXmlStreamReader::Invalid: + m_error = msgReaderError(reader, reader.errorString()); + return false; + case QXmlStreamReader::StartElement: { + const auto elementTypeOpt = elementFromTag(reader.name()); + if (!elementTypeOpt.has_value()) { + m_error = u"Unknown tag name: '"_s + reader.name().toString() + u'\''; + return false; + } + m_stack.push(elementTypeOpt.value()); + if (!startElement(reader, m_stack.top())) { + m_error = msgReaderError(reader, m_error); + return false; + } + } + break; + case QXmlStreamReader::EndElement: + if (!endElement(m_stack.top())) { + m_error = msgReaderError(reader, m_error); + return false; + } + m_stack.pop(); + break; + case QXmlStreamReader::Characters: + if (!characters(reader.text())) { + m_error = msgReaderError(reader, m_error); + return false; + } + break; + case QXmlStreamReader::StartDocument: + case QXmlStreamReader::EndDocument: + case QXmlStreamReader::Comment: + case QXmlStreamReader::DTD: + case QXmlStreamReader::EntityReference: + case QXmlStreamReader::ProcessingInstruction: + break; + } + } + return true; +} + +bool TypeSystemParser::endElement(StackElement element) +{ + if (m_ignoreDepth) { + --m_ignoreDepth; + return true; + } + + if (m_currentDroppedEntryDepth != 0) { + --m_currentDroppedEntryDepth; + return true; + } + + if (element == StackElement::ImportFile) + return true; + + if (m_contextStack.isEmpty()) + return true; + + const auto &top = m_contextStack.top(); + + switch (element) { + case StackElement::Unimplemented: + return true; + case StackElement::Root: + if (m_generate == TypeEntry::GenerateCode) { + TypeDatabase::instance()->addGlobalUserFunctions(top->addedFunctions); + TypeDatabase::instance()->addGlobalUserFunctionModifications(top->functionMods); + for (const auto &customConversion : std::as_const(customConversionsForReview)) { + TargetToNativeConversions &toNatives = + customConversion->targetToNativeConversions(); + for (auto &toNative : toNatives) + toNative.setSourceType(m_context->db->findType(toNative.sourceTypeName())); + } + } + purgeEmptyCodeSnips(&std::static_pointer_cast<TypeSystemTypeEntry>(top->entry)->codeSnips()); + break; + case StackElement::FunctionTypeEntry: + TypeDatabase::instance()->addGlobalUserFunctionModifications(top->functionMods); + break; + case StackElement::ObjectTypeEntry: + case StackElement::ValueTypeEntry: + case StackElement::InterfaceTypeEntry: + case StackElement::ContainerTypeEntry: + case StackElement::NamespaceTypeEntry: { + Q_ASSERT(top->entry); + Q_ASSERT(top->entry->isComplex()); + auto centry = std::static_pointer_cast<ComplexTypeEntry>(top->entry); + purgeEmptyCodeSnips(¢ry->codeSnips()); + centry->setAddedFunctions(top->addedFunctions); + centry->setFunctionModifications(top->functionMods); + centry->setFieldModifications(top->fieldMods); + centry->setDocModification(top->docModifications); + } + break; + + case StackElement::TypedefTypeEntry: { + auto centry = std::static_pointer_cast<TypedefEntry>(top->entry)->target(); + centry->setAddedFunctions(centry->addedFunctions() + top->addedFunctions); + centry->setFunctionModifications(centry->functionModifications() + top->functionMods); + centry->setFieldModifications(centry->fieldModifications() + top->fieldMods); + centry->setDocModification(centry->docModifications() + top->docModifications); + if (top->entry->isComplex()) { + auto cte = std::static_pointer_cast<const ComplexTypeEntry>(top->entry); + centry->setCodeSnips(centry->codeSnips() + cte->codeSnips()); + } + } + break; + + case StackElement::AddFunction: + case StackElement::DeclareFunction: { + // Leaving add-function: Assign all modifications to the added function + const int modIndex = top->addedFunctionModificationIndex; + top->addedFunctionModificationIndex = -1; + Q_ASSERT(modIndex >= 0); + Q_ASSERT(!top->addedFunctions.isEmpty()); + while (modIndex < top->functionMods.size()) + top->addedFunctions.last()->modifications().append(top->functionMods.takeAt(modIndex)); + } + break; + case StackElement::NativeToTarget: + case StackElement::AddConversion: + switch (parserState()) { + case ParserState::PrimitiveTypeNativeToTargetConversion: + case ParserState::PrimitiveTypeTargetToNativeConversion: { + auto customConversion = CustomConversion::getCustomConversion(top->entry); + if (!customConversion) { + m_error = msgMissingCustomConversion(top->entry); + return false; + } + QString code = top->conversionCodeSnips.constLast().code(); + if (element == StackElement::AddConversion) { + if (customConversion->targetToNativeConversions().isEmpty()) { + m_error = u"CustomConversion's target to native conversions missing."_s; + return false; + } + customConversion->targetToNativeConversions().last().setConversion(code); + } else { + customConversion->setNativeToTargetConversion(code); + } + } + break; + + case ParserState::ArgumentNativeToTargetConversion: { + top->conversionCodeSnips.last().language = TypeSystem::TargetLangCode; + auto &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last(); + lastArgMod.conversionRules().append(top->conversionCodeSnips.constLast()); + } + break; + case ParserState::ArgumentTargetToNativeConversion: { + top->conversionCodeSnips.last().language = TypeSystem::NativeCode; + auto &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last(); + lastArgMod.conversionRules().append(top->conversionCodeSnips.constLast()); + } + break; + default: + break; + } + top->conversionCodeSnips.clear(); + break; + + case StackElement::EnumTypeEntry: + m_currentEnum = nullptr; + break; + case StackElement::Template: + m_context->db->addTemplate(m_templateEntry); + m_templateEntry = nullptr; + break; + case StackElement::InsertTemplate: + if (auto *snip = injectCodeTarget(1)) + snip->addTemplateInstance(m_templateInstance); + m_templateInstance.reset(); + break; + + case StackElement::ModifyArgument: + purgeEmptyCodeSnips(&top->functionMods.last().argument_mods().last().conversionRules()); + break; + + default: + break; + } + + if (isTypeEntry(element) || element == StackElement::Root) + m_contextStack.pop(); + + return true; +} + +ParserState TypeSystemParser::parserState(qsizetype offset) const +{ + const auto stackSize = m_stack.size() - offset; + if (stackSize <= 0 || m_contextStack.isEmpty()) + return ParserState::None; + + const auto last = stackSize - 1; + + switch (m_stack.at(last)) { + // Primitive entry with conversion rule + case StackElement::NativeToTarget: // <conversion-rule><native-to-target> + if (stackSize > 2 && m_stack.at(last - 2) == StackElement::ModifyArgument) + return ParserState::ArgumentNativeToTargetConversion; + return ParserState::PrimitiveTypeNativeToTargetConversion; + + case StackElement::AddConversion: // <conversion-rule><target-to-native><add-conversion> + if (stackSize > 3 && m_stack.at(last - 3) == StackElement::ModifyArgument) + return ParserState::ArgumentTargetToNativeConversion; + return ParserState::PrimitiveTypeTargetToNativeConversion; + + case StackElement::ConversionRule: + if (stackSize > 1 && m_stack.at(last - 1) == StackElement::ModifyArgument) + return ParserState::ArgumentConversion; + break; + + case StackElement::InjectCode: + switch (m_stack.value(last - 1, StackElement::None)) { + case StackElement::Root: + return ParserState::TypeSystemCodeInjection; + case StackElement::ModifyFunction: + case StackElement::AddFunction: + return ParserState::FunctionCodeInjection; + case StackElement::NamespaceTypeEntry: + case StackElement::ObjectTypeEntry: + case StackElement::ValueTypeEntry: + case StackElement::InterfaceTypeEntry: + return ParserState::TypeEntryCodeInjection; + default: + break; + } + break; + + case StackElement::Template: + return ParserState::Template; + + default: + break; + } + + return ParserState::None; +} + +// Return where to add injected code depending on elements. +CodeSnipAbstract *TypeSystemParser::injectCodeTarget(qsizetype offset) const +{ + const auto state = parserState(offset); + if (state == ParserState::None) + return nullptr; + + const auto &top = m_contextStack.top(); + switch (state) { + case ParserState::PrimitiveTypeNativeToTargetConversion: + case ParserState::PrimitiveTypeTargetToNativeConversion: + case ParserState::ArgumentNativeToTargetConversion: + case ParserState::ArgumentTargetToNativeConversion: + return &top->conversionCodeSnips.last(); + case ParserState::ArgumentConversion: + return &top->functionMods.last().argument_mods().last().conversionRules().last(); + case ParserState::FunctionCodeInjection: { + auto &funcMod = top->functionMods.last(); + funcMod.setModifierFlag(FunctionModification::CodeInjection); + return &funcMod.snips().last(); + } + case ParserState::TypeEntryCodeInjection: + Q_ASSERT(top->entry->isComplex()); + return &std::static_pointer_cast<ComplexTypeEntry>(top->entry)->codeSnips().last(); + case ParserState::TypeSystemCodeInjection: + Q_ASSERT(top->entry->isTypeSystem()); + return &std::static_pointer_cast<TypeSystemTypeEntry>(top->entry)->codeSnips().last(); + case ParserState::Template: + return m_templateEntry.get(); + default: + break; + } + + return nullptr; +} + +template <class String> // QString/QStringRef +bool TypeSystemParser::characters(const String &ch) +{ + const auto stackSize = m_stack.size(); + if (m_currentDroppedEntryDepth != 0 || m_ignoreDepth != 0 + || stackSize == 0 || m_stack.top() == StackElement::Unimplemented) { + return true; + } + + const StackElement type = m_stack.top(); + + if (type == StackElement::Template) { + m_templateEntry->addCode(ch); + return true; + } + + if (m_contextStack.isEmpty()) { + m_error = msgNoRootTypeSystemEntry(); + return false; + } + + if (auto *snip = injectCodeTarget()) { + snip->addCode(ch); + return true; + } + + if (isDocumentation(type)) { + const bool isAddedFunction = m_stack.value(m_stack.size() - 2, StackElement::None) + == StackElement::AddFunction; + const auto &top = m_contextStack.top(); + auto &docModifications = isAddedFunction + ? top->addedFunctions.last()->docModifications() + : top->docModifications; + docModifications.last().setCode(ch); + } + + return true; +} + +bool TypeSystemParser::importFileElement(const QXmlStreamAttributes &atts) +{ + const QString fileName = atts.value(nameAttribute).toString(); + if (fileName.isEmpty()) { + m_error = u"Required attribute 'name' missing for include-file tag."_s; + return false; + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + file.setFileName(u":/trolltech/generator/"_s + fileName); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + m_error = msgCannotOpenForReading(file); + return false; + } + } + + const auto quoteFrom = atts.value(quoteAfterLineAttribute); + bool foundFromOk = quoteFrom.isEmpty(); + bool from = quoteFrom.isEmpty(); + + const auto quoteTo = atts.value(quoteBeforeLineAttribute); + bool foundToOk = quoteTo.isEmpty(); + bool to = true; + + QTextStream in(&file); + while (!in.atEnd()) { + QString line = in.readLine(); + if (from && to && line.contains(quoteTo)) { + to = false; + foundToOk = true; + break; + } + if (from && to) + characters(line + u'\n'); + if (!from && line.contains(quoteFrom)) { + from = true; + foundFromOk = true; + } + } + if (!foundFromOk || !foundToOk) { + QString fromError = QString::fromLatin1("Could not find quote-after-line='%1' in file '%2'.") + .arg(quoteFrom.toString(), fileName); + QString toError = QString::fromLatin1("Could not find quote-before-line='%1' in file '%2'.") + .arg(quoteTo.toString(), fileName); + + if (!foundToOk) + m_error = toError; + if (!foundFromOk) + m_error = fromError; + if (!foundFromOk && !foundToOk) + m_error = fromError + u' ' + toError; + return false; + } + + return true; +} + +static bool convertBoolean(QStringView value, QAnyStringView attributeName, bool defaultValue) +{ + if (value.compare(trueAttributeValue, Qt::CaseInsensitive) == 0 + || value.compare(yesAttributeValue, Qt::CaseInsensitive) == 0) { + return true; + } + if (value.compare(falseAttributeValue, Qt::CaseInsensitive) == 0 + || value.compare(noAttributeValue, Qt::CaseInsensitive) == 0) { + return false; + } + qCWarning(lcShiboken).noquote().nospace() << "Boolean value '" << value + << "' not supported in attribute '" << attributeName + << "'. Use 'yes' or 'no'. Defaulting to '" + << (defaultValue ? yesAttributeValue : noAttributeValue) << "'."; + return defaultValue; +} + +static bool convertRemovalAttribute(QStringView value) +{ + return value == u"all" // Legacy + || convertBoolean(value, removeAttribute, false); +} + +// Check whether an entry should be dropped, allowing for dropping the module +// name (match 'Class' and 'Module.Class'). +static bool shouldDropTypeEntry(const TypeDatabase *db, + const TypeSystemParser::ContextStack &stack , + QString name) +{ + for (auto i = stack.size() - 1; i >= 0; --i) { + if (auto entry = stack.at(i)->entry) { + if (entry->type() == TypeEntry::TypeSystemType) { + if (db->shouldDropTypeEntry(name)) // Unqualified + return true; + } + name.prepend(u'.'); + name.prepend(entry->name()); + } + } + return db->shouldDropTypeEntry(name); +} + +// Returns empty string if there's no error. +static QString checkSignatureError(const QString& signature, const QString& tag) +{ + QString funcName = signature.left(signature.indexOf(u'(')).trimmed(); + static const QRegularExpression whiteSpace("\\s"_L1); + Q_ASSERT(whiteSpace.isValid()); + if (!funcName.startsWith(u"operator ") && funcName.contains(whiteSpace)) { + return QString::fromLatin1("Error in <%1> tag signature attribute '%2'.\n" + "White spaces aren't allowed in function names, " + "and return types should not be part of the signature.") + .arg(tag, signature); + } + return QString(); +} + +inline TypeEntryCPtr TypeSystemParser::currentParentTypeEntry() const +{ + const auto size = m_contextStack.size(); + return size > 1 ? m_contextStack.at(size - 2)->entry : nullptr; +} + +bool TypeSystemParser::checkRootElement() +{ + for (auto i = m_contextStack.size() - 1; i >= 0; --i) { + auto e = m_contextStack.at(i)->entry; + if (e && e->isTypeSystem()) + return true; + } + m_error = msgNoRootTypeSystemEntry(); + return false; +} + +static TypeEntryPtr findViewedType(const QString &name) +{ + const auto range = TypeDatabase::instance()->entries().equal_range(name); + for (auto i = range.first; i != range.second; ++i) { + switch (i.value()->type()) { + case TypeEntry::BasicValueType: + case TypeEntry::PrimitiveType: + case TypeEntry::ContainerType: + case TypeEntry::ObjectType: + return i.value(); + default: + break; + } + } + return nullptr; +} + +bool TypeSystemParser::applyCommonAttributes(const ConditionalStreamReader &reader, + const TypeEntryPtr &type, + QXmlStreamAttributes *attributes) +{ + type->setSourceLocation(SourceLocation(m_currentFile, + reader.lineNumber())); + type->setCodeGeneration(m_generate); + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"revision") { + type->setRevision(attributes->takeAt(i).value().toInt()); + } else if (name == u"view-on") { + const QString name = attributes->takeAt(i).value().toString(); + TypeEntryPtr views = findViewedType(name); + if (!views) { + m_error = msgCannotFindView(name, type->name()); + return false; + } + type->setViewOn(views); + } + } + return true; +} + +CustomTypeEntryPtr TypeSystemParser::parseCustomTypeEntry(const ConditionalStreamReader &, + const QString &name, + const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + auto result = std::make_shared<CustomTypeEntry>(name, since, m_contextStack.top()->entry); + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == checkFunctionAttribute) + result->setCheckFunction(attributes->takeAt(i).value().toString()); + } + return result; +} + +FlagsTypeEntryPtr + TypeSystemParser::parseFlagsEntry(const ConditionalStreamReader &reader, + const EnumTypeEntryPtr &enumEntry, QString flagName, + const QVersionNumber &since, + QXmlStreamAttributes *attributes) + +{ + if (!checkRootElement()) + return nullptr; + auto ftype = std::make_shared<FlagsTypeEntry>(u"QFlags<"_s + enumEntry->name() + u'>', + since, + typeSystemTypeEntry(currentParentTypeEntry())); + ftype->setOriginator(enumEntry); + ftype->setTargetLangPackage(enumEntry->targetLangPackage()); + // Try toenumEntry get the guess the qualified flag name + if (!flagName.contains(u"::"_s)) { + auto eq = enumEntry->qualifier(); + if (!eq.isEmpty()) + flagName.prepend(eq + u"::"_s); + } + + ftype->setOriginalName(flagName); + if (!applyCommonAttributes(reader, ftype, attributes)) + return nullptr; + + QStringList lst = flagName.split(u"::"_s); + const QString name = lst.takeLast(); + const QString targetLangFlagName = lst.join(u'.'); + const QString &targetLangQualifier = enumEntry->targetLangQualifier(); + if (targetLangFlagName != targetLangQualifier) { + qCWarning(lcShiboken, "enum %s and flags %s (%s) differ in qualifiers", + qPrintable(targetLangQualifier), qPrintable(lst.value(0)), + qPrintable(targetLangFlagName)); + } + + ftype->setFlagsName(name); + enumEntry->setFlags(ftype); + + m_context->db->addFlagsType(ftype); + m_context->db->addType(ftype); + + const int revisionIndex = + indexOfAttribute(*attributes, u"flags-revision"); + ftype->setRevision(revisionIndex != -1 + ? attributes->takeAt(revisionIndex).value().toInt() + : enumEntry->revision()); + return ftype; +} + +SmartPointerTypeEntryPtr + TypeSystemParser::parseSmartPointerEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + TypeSystem::SmartPointerType smartPointerType = TypeSystem::SmartPointerType::Shared; + QString getter; + QString refCountMethodName; + QString valueCheckMethod; + QString nullCheckMethod; + QString resetMethod; + QString instantiations; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"type") { + const auto attribute = attributes->takeAt(i); + const auto typeOpt = smartPointerTypeFromAttribute(attribute.value()); + if (!typeOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return nullptr; + } + smartPointerType = typeOpt.value(); + } else if (name == u"getter") { + getter = attributes->takeAt(i).value().toString(); + } else if (name == u"ref-count-method") { + refCountMethodName = attributes->takeAt(i).value().toString(); + } else if (name == u"instantiations") { + instantiations = attributes->takeAt(i).value().toString(); + } else if (name == u"value-check-method") { + valueCheckMethod = attributes->takeAt(i).value().toString(); + } else if (name == u"null-check-method") { + nullCheckMethod = attributes->takeAt(i).value().toString(); + } else if (name == u"reset-method") { + resetMethod = attributes->takeAt(i).value().toString(); + } + } + + if (getter.isEmpty()) { + m_error = u"No function getter name specified for getting the raw pointer held by the smart pointer."_s; + return nullptr; + } + + QString signature = getter + u"()"_s; + signature = TypeDatabase::normalizedSignature(signature); + if (signature.isEmpty()) { + m_error = u"No signature for the smart pointer getter found."_s; + return nullptr; + } + + QString errorString = checkSignatureError(signature, + u"smart-pointer-type"_s); + if (!errorString.isEmpty()) { + m_error = errorString; + return nullptr; + } + + if (smartPointerType == TypeSystem::SmartPointerType::Unique && resetMethod.isEmpty()) { + m_error = u"Unique pointers require a reset() method."_s; + return nullptr; + } + + auto type = std::make_shared<SmartPointerTypeEntry>(name, getter, smartPointerType, + refCountMethodName, since, + currentParentTypeEntry()); + if (!applyCommonAttributes(reader, type, attributes)) + return nullptr; + applyComplexTypeAttributes(reader, type, attributes); + type->setNullCheckMethod(nullCheckMethod); + type->setValueCheckMethod(valueCheckMethod); + type->setResetMethod(resetMethod); + m_context->smartPointerInstantiations.insert(type, instantiations); + return type; +} + +PrimitiveTypeEntryPtr + TypeSystemParser::parsePrimitiveTypeEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + auto type = std::make_shared<PrimitiveTypeEntry>(name, since, currentParentTypeEntry()); + QString targetLangApiName; + if (!applyCommonAttributes(reader, type, attributes)) + return nullptr; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == targetLangNameAttribute) { + type->setTargetLangName(attributes->takeAt(i).value().toString()); + } else if (name == u"target-lang-api-name") { + targetLangApiName = attributes->takeAt(i).value().toString(); + } else if (name == preferredConversionAttribute) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } else if (name == preferredTargetLangTypeAttribute) { + const bool v = convertBoolean(attributes->takeAt(i).value(), + preferredTargetLangTypeAttribute, true); + type->setPreferredTargetLangType(v); + } else if (name == u"default-constructor") { + type->setDefaultConstructor(attributes->takeAt(i).value().toString()); + } + } + + if (!targetLangApiName.isEmpty()) { + auto e = m_context->db->findType(targetLangApiName); + if (!e || !e->isCustom()) { + m_error = msgInvalidTargetLanguageApiName(targetLangApiName); + return nullptr; + } + type->setTargetLangApiType(std::static_pointer_cast<CustomTypeEntry>(e)); + } + type->setTargetLangPackage(m_defaultPackage); + return type; +} + +// "int:QList_int;QString:QList_QString" +bool TypeSystemParser::parseOpaqueContainers(QStringView s, OpaqueContainers *result) +{ + const auto entries = s.split(u';'); + for (const auto &entry : entries) { + const auto values = entry.split(u':'); + if (values.size() != 2) { + m_error = u"Error parsing the opaque container attribute: \""_s + + s.toString() + u"\"."_s; + return false; + } + OpaqueContainer oc; + oc.name = values.at(1).trimmed().toString(); + const auto instantiations = values.at(0).split(u',', Qt::SkipEmptyParts); + for (const auto &instantiationV : instantiations) { + QString instantiation = instantiationV.trimmed().toString(); + // Fix to match AbstractMetaType::signature() which is used for matching + // "Foo*" -> "Foo *" + const auto asteriskPos = instantiation.indexOf(u'*'); + if (asteriskPos > 0 && !instantiation.at(asteriskPos - 1).isSpace()) + instantiation.insert(asteriskPos, u' '); + oc.instantiations.append(instantiation); + } + result->append(oc); + } + return true; +} + +ContainerTypeEntryPtr + TypeSystemParser::parseContainerTypeEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + const auto typeIndex = indexOfAttribute(*attributes, u"type"); + if (typeIndex == -1) { + m_error = u"no 'type' attribute specified"_s; + return nullptr; + } + const auto typeName = attributes->at(typeIndex).value(); + const auto containerTypeOpt = containerTypeFromAttribute(typeName); + if (!containerTypeOpt.has_value()) { + m_error = u"there is no container of type "_s + typeName.toString(); + return nullptr; + } + attributes->removeAt(typeIndex); + auto type = std::make_shared<ContainerTypeEntry>(name, containerTypeOpt.value(), + since, currentParentTypeEntry()); + if (!applyCommonAttributes(reader, type, attributes)) + return nullptr; + applyComplexTypeAttributes(reader, type, attributes); + + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == opaqueContainerAttribute) { + const auto attribute = attributes->takeAt(i); + OpaqueContainers oc; + if (!parseOpaqueContainers(attribute.value(), &oc)) + return nullptr; + type->appendOpaqueContainers(oc); + } + } + + return type; +} + +bool TypeSystemParser::parseOpaqueContainerElement(QXmlStreamAttributes *attributes) +{ + QString containerName; + OpaqueContainers oc; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == nameAttribute) { + containerName = attributes->takeAt(i).value().toString(); + } else if (name == opaqueContainerAttribute) { + const auto attribute = attributes->takeAt(i); + if (!parseOpaqueContainers(attribute.value(), &oc)) + return false; + } + } + if (containerName.isEmpty()) { + m_error = msgMissingAttribute(nameAttribute); + return false; + } + m_context->opaqueContainerHash[containerName].append(oc); + return true; +} + +EnumTypeEntryPtr + TypeSystemParser::parseEnumTypeEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + auto entry = std::make_shared<EnumTypeEntry>(name, since, currentParentTypeEntry()); + applyCommonAttributes(reader, entry, attributes); + entry->setTargetLangPackage(m_defaultPackage); + + QString flagNames; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"upper-bound") { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } else if (name == u"lower-bound") { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } else if (name == docFileAttribute) { + entry->setDocFile(attributes->takeAt(i).value().toString()); + } else if (name == forceIntegerAttribute) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } else if (name == pythonEnumTypeAttribute) { + const auto attribute = attributes->takeAt(i); + const auto typeOpt = pythonEnumTypeFromAttribute(attribute.value()); + if (typeOpt.has_value()) { + entry->setPythonEnumType(typeOpt.value()); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == cppEnumTypeAttribute) { + entry->setCppType(attributes->takeAt(i).value().toString()); + } else if (name == extensibleAttribute) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } else if (name == flagsAttribute) { + flagNames = attributes->takeAt(i).value().toString(); + } + } + + // put in the flags parallel... + if (!flagNames.isEmpty()) { + const QStringList &flagNameList = flagNames.split(u','); + for (const QString &flagName : flagNameList) + parseFlagsEntry(reader, entry, flagName.trimmed(), since, attributes); + } + return entry; +} + + +NamespaceTypeEntryPtr + TypeSystemParser::parseNamespaceTypeEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + auto result = std::make_shared<NamespaceTypeEntry>(name, since, currentParentTypeEntry()); + auto visibility = TypeSystem::Visibility::Unspecified; + applyCommonAttributes(reader, result, attributes); + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto attributeName = attributes->at(i).qualifiedName(); + if (attributeName == u"files") { + const QString pattern = attributes->takeAt(i).value().toString(); + QRegularExpression re(pattern); + if (!re.isValid()) { + m_error = msgInvalidRegularExpression(pattern, re.errorString()); + return nullptr; + } + result->setFilePattern(re); + } else if (attributeName == u"extends") { + const auto extendsPackageName = attributes->at(i).value(); + auto allEntries = TypeDatabase::instance()->findNamespaceTypes(name); + auto extendsIt = std::find_if(allEntries.cbegin(), allEntries.cend(), + [extendsPackageName] (const NamespaceTypeEntryCPtr &e) { + return e->targetLangPackage() == extendsPackageName; + }); + if (extendsIt == allEntries.cend()) { + m_error = msgCannotFindNamespaceToExtend(name, extendsPackageName.toString()); + return nullptr; + } + result->setExtends(*extendsIt); + attributes->removeAt(i); + } else if (attributeName == visibleAttribute) { + const auto attribute = attributes->takeAt(i); + const auto visibilityOpt = visibilityFromAttribute(attribute.value()); + if (!visibilityOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return nullptr; + } + visibility = visibilityOpt.value(); + } else if (attributeName == generateAttribute) { + if (!convertBoolean(attributes->takeAt(i).value(), generateAttribute, true)) + visibility = TypeSystem::Visibility::Invisible; + } else if (attributeName == generateUsingAttribute) { + result->setGenerateUsing(convertBoolean(attributes->takeAt(i).value(), + generateUsingAttribute, true)); + } + } + + if (visibility != TypeSystem::Visibility::Unspecified) + result->setVisibility(visibility); + // Handle legacy "generate" before the common handling + applyComplexTypeAttributes(reader, result, attributes); + + if (result->extends() && !result->hasPattern()) { + m_error = msgExtendingNamespaceRequiresPattern(name); + return {}; + } + + return result; +} + +ValueTypeEntryPtr + TypeSystemParser::parseValueTypeEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + auto typeEntry = std::make_shared<ValueTypeEntry>(name, since, currentParentTypeEntry()); + if (!applyCommonAttributes(reader, typeEntry, attributes)) + return nullptr; + applyComplexTypeAttributes(reader, typeEntry, attributes); + const int defaultCtIndex = + indexOfAttribute(*attributes, u"default-constructor"); + if (defaultCtIndex != -1) + typeEntry->setDefaultConstructor(attributes->takeAt(defaultCtIndex).value().toString()); + return typeEntry; +} + +FunctionTypeEntryPtr + TypeSystemParser::parseFunctionTypeEntry(const ConditionalStreamReader &reader, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + + FunctionModification mod; + const auto oldAttributesSize = attributes->size(); + if (!parseModifyFunctionAttributes(attributes, &mod)) + return nullptr; + const bool hasModification = attributes->size() < oldAttributesSize; + + QString originalSignature; + QString docFile; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == signatureAttribute) + originalSignature = attributes->takeAt(i).value().toString().simplified(); + else if (name == docFileAttribute) + docFile = attributes->takeAt(i).value().toString(); + } + + const QString signature = TypeDatabase::normalizedSignature(originalSignature); + if (signature.isEmpty()) { + m_error = msgMissingAttribute(signatureAttribute); + return nullptr; + } + + if (hasModification) { + mod.setOriginalSignature(originalSignature); + mod.setSignature(signature); + m_contextStack.top()->functionMods << mod; + } + + TypeEntryPtr existingType = m_context->db->findType(name); + + if (!existingType) { + auto result = std::make_shared<FunctionTypeEntry>(name, signature, since, + currentParentTypeEntry()); + result->setTargetLangPackage(m_defaultPackage); + result->setDocFile(docFile); + applyCommonAttributes(reader, result, attributes); + return result; + } + + if (existingType->type() != TypeEntry::FunctionType) { + m_error = name + " expected to be a function, but isn't! Maybe it was already declared as a class or something else."_L1; + return nullptr; + } + + auto result = std::static_pointer_cast<FunctionTypeEntry>(existingType); + result->addSignature(signature); + return result; +} + +TypedefEntryPtr + TypeSystemParser::parseTypedefEntry(const ConditionalStreamReader &reader, + const QString &name, StackElement topElement, + const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + if (!checkRootElement()) + return nullptr; + if (topElement != StackElement::Root + && topElement != StackElement::NamespaceTypeEntry) { + m_error = u"typedef entries must be nested in namespaces or type system."_s; + return nullptr; + } + const auto sourceIndex = indexOfAttribute(*attributes, sourceAttribute); + if (sourceIndex == -1) { + m_error = msgMissingAttribute(sourceAttribute); + return nullptr; + } + const QString sourceType = attributes->takeAt(sourceIndex).value().toString(); + auto result = std::make_shared<TypedefEntry>(name, sourceType, since, + currentParentTypeEntry()); + if (!applyCommonAttributes(reader, result, attributes)) + return nullptr; + applyComplexTypeAttributes(reader, result, attributes); + return result; +} + +void TypeSystemParser::applyComplexTypeAttributes(const ConditionalStreamReader &reader, + const ComplexTypeEntryPtr &ctype, + QXmlStreamAttributes *attributes) const +{ + bool generate = true; + ctype->setCopyable(ComplexTypeEntry::Unknown); + auto exceptionHandling = m_exceptionHandling; + auto allowThread = m_allowThread; + + QString package = m_defaultPackage; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == streamAttribute) { + ctype->setStream(convertBoolean(attributes->takeAt(i).value(), streamAttribute, false)); + } else if (name == privateAttribute) { + ctype->setPrivate(convertBoolean(attributes->takeAt(i).value(), + privateAttribute, false)); + } else if (name == generateAttribute) { + generate = convertBoolean(attributes->takeAt(i).value(), generateAttribute, true); + } else if (name ==packageAttribute) { + package = attributes->takeAt(i).value().toString(); + } else if (name == defaultSuperclassAttribute) { + ctype->setDefaultSuperclass(attributes->takeAt(i).value().toString()); + } else if (name == genericClassAttribute) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + const bool v = convertBoolean(attributes->takeAt(i).value(), + genericClassAttribute, false); + ctype->setGenericClass(v); + } else if (name == targetLangNameAttribute) { + ctype->setTargetLangName(attributes->takeAt(i).value().toString()); + } else if (name == polymorphicBaseAttribute) { + const bool v = convertBoolean(attributes->takeAt(i).value(), + polymorphicBaseAttribute, false); + ctype->setIsPolymorphicBase(v); + } else if (name == u"polymorphic-name-function") { + ctype->setPolymorphicNameFunction(attributes->takeAt(i).value().toString()); + } else if (name == u"polymorphic-id-expression") { + ctype->setPolymorphicIdValue(attributes->takeAt(i).value().toString()); + } else if (name == copyableAttribute) { + const bool v = convertBoolean(attributes->takeAt(i).value(), + copyableAttribute, false); + ctype->setCopyable(v ? ComplexTypeEntry::CopyableSet : ComplexTypeEntry::NonCopyableSet); + } else if (name == exceptionHandlingAttribute) { + const auto attribute = attributes->takeAt(i); + const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value()); + if (exceptionOpt.has_value()) { + exceptionHandling = exceptionOpt.value(); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == allowThreadAttribute) { + const auto attribute = attributes->takeAt(i); + const auto allowThreadOpt = allowThreadFromAttribute(attribute.value()); + if (allowThreadOpt.has_value()) { + allowThread = allowThreadOpt.value(); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == u"held-type") { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } else if (name == u"hash-function") { + ctype->setHashFunction(attributes->takeAt(i).value().toString()); + } else if (name == forceAbstractAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), forceAbstractAttribute, false)) + ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::ForceAbstract); + } else if (name == deprecatedAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), deprecatedAttribute, false)) + ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::Deprecated); + } else if (name == disableWrapperAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), disableWrapperAttribute, false)) + ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::DisableWrapper); + } else if (name == deleteInMainThreadAttribute) { + if (convertBoolean(attributes->takeAt(i).value(), deleteInMainThreadAttribute, false)) + ctype->setDeleteInMainThread(true); + } else if (name == qtMetaObjectFunctionsAttribute) { + if (!convertBoolean(attributes->takeAt(i).value(), + qtMetaObjectFunctionsAttribute, true)) { + ctype->setTypeFlags(ctype->typeFlags() + | ComplexTypeEntry::DisableQtMetaObjectFunctions); + } + } else if (name == generateFunctionsAttribute) { + const auto names = attributes->takeAt(i).value(); + const auto nameList = names.split(u';', Qt::SkipEmptyParts); + QSet<QString> nameSet; + for (const auto &name : nameList) + nameSet.insert(name.trimmed().toString()); + ctype->setGenerateFunctions(nameSet); + } else if (name == u"target-type") { + ctype->setTargetType(attributes->takeAt(i).value().toString()); + } else if (name == snakeCaseAttribute) { + const auto attribute = attributes->takeAt(i); + const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); + if (snakeCaseOpt.has_value()) { + ctype->setSnakeCase(snakeCaseOpt.value()); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == isNullAttribute) { + const auto attribute = attributes->takeAt(i); + const auto boolCastOpt = boolCastFromAttribute(attribute.value()); + if (boolCastOpt.has_value()) { + ctype->setIsNullMode(boolCastOpt.value()); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == operatorBoolAttribute) { + const auto attribute = attributes->takeAt(i); + const auto boolCastOpt = boolCastFromAttribute(attribute.value()); + if (boolCastOpt.has_value()) { + ctype->setOperatorBoolMode(boolCastOpt.value()); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == qtMetaTypeAttribute) { + const auto attribute = attributes->takeAt(i); + const auto qtMetaTypeOpt = qtMetaTypeFromAttribute(attribute.value()); + if (qtMetaTypeOpt.has_value()) { + ctype->setQtMetaTypeRegistration(qtMetaTypeOpt.value()); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == parentManagementAttribute) { + const auto attribute = attributes->takeAt(i); + if (convertBoolean(attribute.value(), parentManagementAttribute, false)) + ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::ParentManagement); + ComplexTypeEntry::setParentManagementEnabled(true); + } + } + + if (exceptionHandling != TypeSystem::ExceptionHandling::Unspecified) + ctype->setExceptionHandling(exceptionHandling); + if (allowThread != TypeSystem::AllowThread::Unspecified) + ctype->setAllowThread(allowThread); + + // The generator code relies on container's package being empty. + if (ctype->type() != TypeEntry::ContainerType) + ctype->setTargetLangPackage(package); + + if (generate) + ctype->setCodeGeneration(m_generate); + else + ctype->setCodeGeneration(TypeEntry::GenerationDisabled); +} + +bool TypeSystemParser::parseConfiguration(StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (!isComplexTypeEntry(topElement) + && topElement != StackElement::EnumTypeEntry) { + m_error = u"<configuration> must be nested into a complex or enum type entry."_s; + return false; + } + QString condition; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"condition") { + condition = attributes->takeAt(i).value().toString(); + } + } + if (condition.isEmpty()) { + m_error = u"<configuration> requires a \"condition\" attribute."_s; + return false; + } + const auto topEntry = m_contextStack.top()->entry; + const auto configurableEntry = std::dynamic_pointer_cast<ConfigurableTypeEntry>(topEntry); + Q_ASSERT(configurableEntry); + configurableEntry->setConfigCondition(condition); + return true; +} + +bool TypeSystemParser::parseRenameFunction(const ConditionalStreamReader &, + QString *name, QXmlStreamAttributes *attributes) +{ + QString signature; + QString rename; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == signatureAttribute) { + // Do not remove as it is needed for the type entry later on + signature = attributes->at(i).value().toString().simplified(); + } else if (name == renameAttribute) { + rename = attributes->takeAt(i).value().toString(); + } + } + + if (signature.isEmpty()) { + m_error = msgMissingAttribute(signatureAttribute); + return false; + } + + *name = signature.left(signature.indexOf(u'(')).trimmed(); + + QString errorString = checkSignatureError(signature, u"function"_s); + if (!errorString.isEmpty()) { + m_error = errorString; + return false; + } + + if (!rename.isEmpty()) { + static const QRegularExpression functionNameRegExp(u"^[a-zA-Z_][a-zA-Z0-9_]*$"_s); + Q_ASSERT(functionNameRegExp.isValid()); + if (!functionNameRegExp.match(rename).hasMatch()) { + m_error = u"can not rename '"_s + signature + u"', '"_s + + rename + u"' is not a valid function name"_s; + return false; + } + FunctionModification mod; + if (!mod.setSignature(signature, &m_error)) + return false; + mod.setRenamedToName(rename); + mod.setModifierFlag(FunctionModification::Rename); + m_contextStack.top()->functionMods << mod; + } + return true; +} + +bool TypeSystemParser::parseInjectDocumentation(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *attributes) +{ + const bool isAddFunction = topElement == StackElement::AddFunction; + const bool validParent = isTypeEntry(topElement) + || topElement == StackElement::ModifyFunction + || topElement == StackElement::ModifyField + || isAddFunction; + if (!validParent) { + m_error = u"inject-documentation must be inside modify-function, add-function" + "modify-field or other tags that creates a type"_s; + return false; + } + + TypeSystem::DocModificationMode mode = TypeSystem::DocModificationReplace; + TypeSystem::Language lang = TypeSystem::NativeCode; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"mode") { + const auto attribute = attributes->takeAt(i); + const auto modeOpt = docModificationFromAttribute(attribute.value()); + if (!modeOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + mode = modeOpt.value(); + } else if (name == formatAttribute) { + const auto attribute = attributes->takeAt(i); + const auto langOpt = languageFromAttribute(attribute.value()); + if (!langOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + lang = langOpt.value(); + } + } + + QString signature = isTypeEntry(topElement) ? QString() : m_currentSignature; + DocModification mod(mode, signature); + mod.setFormat(lang); + if (hasFileSnippetAttributes(attributes)) { + const auto snippetOptional = readFileSnippet(attributes); + if (!snippetOptional.has_value()) + return false; + mod.setCode(snippetOptional.value().content); + } + auto &top = m_contextStack.top(); + if (isAddFunction) + top->addedFunctions.last()->addDocModification(mod); + else + top->docModifications << mod; + return true; +} + +bool TypeSystemParser::parseModifyDocumentation(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + const bool validParent = isTypeEntry(topElement) + || topElement == StackElement::ModifyFunction + || topElement == StackElement::ModifyField; + if (!validParent) { + m_error = u"modify-documentation must be inside modify-function, " + "modify-field or other tags that creates a type"_s; + return false; + } + + const auto xpathIndex = indexOfAttribute(*attributes, xPathAttribute); + if (xpathIndex == -1) { + m_error = msgMissingAttribute(xPathAttribute); + return false; + } + + const QString xpath = attributes->takeAt(xpathIndex).value().toString(); + QString signature = isTypeEntry(topElement) ? QString() : m_currentSignature; + m_contextStack.top()->docModifications + << DocModification(xpath, signature); + return true; +} + +// m_exceptionHandling +TypeSystemTypeEntryPtr TypeSystemParser::parseRootElement(const ConditionalStreamReader &, + const QVersionNumber &since, + QXmlStreamAttributes *attributes) +{ + TypeSystem::SnakeCase snakeCase = TypeSystem::SnakeCase::Unspecified; + QString subModuleOf; + QString namespaceBegin; + QString namespaceEnd; + + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == packageAttribute) { + m_defaultPackage = attributes->takeAt(i).value().toString(); + } else if (name == defaultSuperclassAttribute) { + m_defaultSuperclass = attributes->takeAt(i).value().toString(); + } else if (name == exceptionHandlingAttribute) { + const auto attribute = attributes->takeAt(i); + const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value()); + if (exceptionOpt.has_value()) { + m_exceptionHandling = exceptionOpt.value(); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == allowThreadAttribute) { + const auto attribute = attributes->takeAt(i); + const auto allowThreadOpt = allowThreadFromAttribute(attribute.value()); + if (allowThreadOpt.has_value()) { + m_allowThread = allowThreadOpt.value(); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == snakeCaseAttribute) { + const auto attribute = attributes->takeAt(i); + const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); + if (snakeCaseOpt.has_value()) { + snakeCase = snakeCaseOpt.value(); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } else if (name == subModuleOfAttribute) { + subModuleOf = attributes->takeAt(i).value().toString(); + } else if (name == "namespace-begin"_L1) { + namespaceBegin = attributes->takeAt(i).value().toString(); + } else if (name == "namespace-end"_L1) { + namespaceEnd = attributes->takeAt(i).value().toString(); + } + } + + if (m_defaultPackage.isEmpty()) { // Extending default, see addBuiltInContainerTypes() + auto moduleEntry = std::const_pointer_cast<TypeSystemTypeEntry>(m_context->db->defaultTypeSystemType()); + Q_ASSERT(moduleEntry); + m_defaultPackage = moduleEntry->name(); + return moduleEntry; + } + + auto moduleEntry = + std::const_pointer_cast<TypeSystemTypeEntry>(m_context->db->findTypeSystemType(m_defaultPackage)); + const bool add = !moduleEntry; + if (add) { + moduleEntry.reset(new TypeSystemTypeEntry(m_defaultPackage, since, + currentParentTypeEntry())); + moduleEntry->setSubModule(subModuleOf); + } + moduleEntry->setCodeGeneration(m_generate); + moduleEntry->setSnakeCase(snakeCase); + if (!namespaceBegin.isEmpty()) + moduleEntry->setNamespaceBegin(namespaceBegin); + if (!namespaceEnd.isEmpty()) + moduleEntry->setNamespaceEnd(namespaceEnd); + + if ((m_generate == TypeEntry::GenerateForSubclass || + m_generate == TypeEntry::GenerateNothing) && !m_defaultPackage.isEmpty()) + TypeDatabase::instance()->addRequiredTargetImport(m_defaultPackage); + + if (add) + m_context->db->addTypeSystemType(moduleEntry); + return moduleEntry; +} + +bool TypeSystemParser::loadTypesystem(const ConditionalStreamReader &, + QXmlStreamAttributes *attributes) +{ + QString typeSystemName; + bool generateChild = true; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == nameAttribute) + typeSystemName = attributes->takeAt(i).value().toString(); + else if (name == generateAttribute) + generateChild = convertBoolean(attributes->takeAt(i).value(), generateAttribute, true); + } + if (typeSystemName.isEmpty()) { + m_error = u"No typesystem name specified"_s; + return false; + } + const bool result = + m_context->db->parseFile(m_context, typeSystemName, m_currentPath, + generateChild && m_generate == TypeEntry::GenerateCode); + if (!result) + m_error = u"Failed to parse: '"_s + typeSystemName + u'\''; + return result; +} + +bool TypeSystemParser::parseRejectEnumValue(const ConditionalStreamReader &, + QXmlStreamAttributes *attributes) +{ + if (!m_currentEnum) { + m_error = u"<reject-enum-value> node must be used inside a <enum-type> node"_s; + return false; + } + const auto nameIndex = indexOfAttribute(*attributes, nameAttribute); + if (nameIndex == -1) { + m_error = msgMissingAttribute(nameAttribute); + return false; + } + m_currentEnum->addEnumValueRejection(attributes->takeAt(nameIndex).value().toString()); + return true; +} + +bool TypeSystemParser::parseReplaceArgumentType(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument) { + m_error = u"Type replacement can only be specified for argument modifications"_s; + return false; + } + const auto modifiedTypeIndex = indexOfAttribute(*attributes, modifiedTypeAttribute); + if (modifiedTypeIndex == -1) { + m_error = u"Type replacement requires 'modified-type' attribute"_s; + return false; + } + m_contextStack.top()->functionMods.last().argument_mods().last().setModifiedType( + attributes->takeAt(modifiedTypeIndex).value().toString()); + return true; +} + +bool TypeSystemParser::parseCustomConversion(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument + && topElement != StackElement::ValueTypeEntry + && topElement != StackElement::PrimitiveTypeEntry + && topElement != StackElement::ContainerTypeEntry) { + m_error = u"Conversion rules can only be specified for argument modification, " + "value-type, primitive-type or container-type conversion."_s; + return false; + } + + QString sourceFile; + QString snippetLabel; + TypeSystem::Language lang = TypeSystem::NativeCode; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == classAttribute) { + const auto languageAttribute = attributes->takeAt(i); + const auto langOpt = languageFromAttribute(languageAttribute.value()); + if (!langOpt.has_value()) { + m_error = msgInvalidAttributeValue(languageAttribute); + return false; + } + lang = langOpt.value(); + } else if (name == u"file") { + sourceFile = attributes->takeAt(i).value().toString(); + } else if (name == snippetAttribute) { + snippetLabel = attributes->takeAt(i).value().toString(); + } + } + + const auto &top = m_contextStack.top(); + if (topElement == StackElement::ModifyArgument) { + CodeSnip snip; + snip.language = lang; + top->functionMods.last().argument_mods().last().conversionRules().append(snip); + return true; + } + + ValueTypeEntryPtr valueTypeEntry; + if (top->entry->isValue()) { + valueTypeEntry = std::static_pointer_cast<ValueTypeEntry>(top->entry); + if (valueTypeEntry->hasTargetConversionRule() || valueTypeEntry->hasCustomConversion()) { + m_error = u"Types can have only one conversion rule"_s; + return false; + } + } + + // The old conversion rule tag that uses a file containing the conversion + // will be kept temporarily for compatibility reasons. FIXME PYSIDE7: Remove + if (valueTypeEntry != nullptr && !sourceFile.isEmpty()) { + if (m_generate != TypeEntry::GenerateForSubclass + && m_generate != TypeEntry::GenerateNothing) { + qWarning(lcShiboken, "Specifying conversion rules by \"file\" is deprecated."); + if (lang != TypeSystem::TargetLangCode) + return true; + + QFile conversionSource(sourceFile); + if (!conversionSource.open(QIODevice::ReadOnly | QIODevice::Text)) { + m_error = msgCannotOpenForReading(conversionSource); + return false; + } + const auto conversionRuleOptional = + extractSnippet(QString::fromUtf8(conversionSource.readAll()), snippetLabel); + if (!conversionRuleOptional.has_value()) { + m_error = msgCannotFindSnippet(sourceFile, snippetLabel); + return false; + } + valueTypeEntry->setTargetConversionRule(conversionRuleOptional.value()); + } + return true; + } + + auto customConversion = std::make_shared<CustomConversion>(top->entry); + if (top->entry->isPrimitive()) + std::static_pointer_cast<PrimitiveTypeEntry>(top->entry)->setCustomConversion(customConversion); + else if (top->entry->isContainer()) + std::static_pointer_cast<ContainerTypeEntry>(top->entry)->setCustomConversion(customConversion); + else if (top->entry->isValue()) + std::static_pointer_cast<ValueTypeEntry>(top->entry)->setCustomConversion(customConversion); + customConversionsForReview.append(customConversion); + return true; +} + +bool TypeSystemParser::parseNativeToTarget(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ConversionRule) { + m_error = u"Native to Target conversion code can only be specified for custom conversion rules."_s; + return false; + } + CodeSnip snip; + if (!readCodeSnippet(attributes, &snip)) + return false; + m_contextStack.top()->conversionCodeSnips.append(snip); + return true; +} + +bool TypeSystemParser::parseAddConversion(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::TargetToNative) { + m_error = u"Target to Native conversions can only be added inside 'target-to-native' tags."_s; + return false; + } + QString sourceTypeName; + QString typeCheck; + CodeSnip snip; + if (!readCodeSnippet(attributes, &snip)) + return false; + + const auto &top = m_contextStack.top(); + top->conversionCodeSnips.append(snip); + + if (parserState() == ParserState::ArgumentTargetToNativeConversion) + return true; + + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"type") + sourceTypeName = attributes->takeAt(i).value().toString(); + else if (name == u"check") + typeCheck = attributes->takeAt(i).value().toString(); + } + + if (sourceTypeName.isEmpty()) { + m_error = u"Target to Native conversions must specify the input type with the 'type' attribute."_s; + return false; + } + auto customConversion = CustomConversion::getCustomConversion(top->entry); + if (!customConversion) { + m_error = msgMissingCustomConversion(top->entry); + return false; + } + customConversion->addTargetToNativeConversion(sourceTypeName, typeCheck); + return true; +} + +static bool parseIndex(const QString &index, int *result, QString *errorMessage) +{ + bool ok = false; + *result = index.toInt(&ok); + if (!ok) + *errorMessage = QString::fromLatin1("Cannot convert '%1' to integer").arg(index); + return ok; +} + +static bool parseArgumentIndex(const QString &index, int *result, QString *errorMessage) +{ + if (index == u"return") { + *result = 0; + return true; + } + if (index == u"this") { + *result = -1; + return true; + } + return parseIndex(index, result, errorMessage); +} + +bool TypeSystemParser::parseModifyArgument(const ConditionalStreamReader &, + StackElement topElement, QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyFunction + && topElement != StackElement::AddFunction + && topElement != StackElement::DeclareFunction) { + m_error = u"Argument modification requires <modify-function>," + " <add-function> or <declare-function> as parent, was "_s + + tagFromElement(topElement).toString(); + return false; + } + + QString index; + QString renameTo; + QString pyiType; + bool resetAfterUse = false; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == indexAttribute) { + index = attributes->takeAt(i).value().toString(); + } else if (name == invalidateAfterUseAttribute) { + resetAfterUse = convertBoolean(attributes->takeAt(i).value(), + invalidateAfterUseAttribute, false); + } else if (name == renameAttribute) { + renameTo = attributes->takeAt(i).value().toString(); + } else if (name == pyiTypeAttribute) { + pyiType = attributes->takeAt(i).value().toString(); + } + } + + if (index.isEmpty()) { + m_error = msgMissingAttribute(indexAttribute); + return false; + } + + int idx; + if (!parseArgumentIndex(index, &idx, &m_error)) + return false; + + ArgumentModification argumentModification = ArgumentModification(idx); + argumentModification.setResetAfterUse(resetAfterUse); + argumentModification.setRenamedToName(renameTo); + argumentModification.setPyiType(pyiType); + m_contextStack.top()->functionMods.last().argument_mods().append(argumentModification); + return true; +} + +bool TypeSystemParser::parseNoNullPointer(const ConditionalStreamReader &reader, + StackElement topElement, QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument) { + m_error = u"no-null-pointer requires argument modification as parent"_s; + return false; + } + + ArgumentModification &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last(); + lastArgMod.setNoNullPointers(true); + + const int defaultValueIndex = + indexOfAttribute(*attributes, u"default-value"); + if (defaultValueIndex != -1) { + const QXmlStreamAttribute attribute = attributes->takeAt(defaultValueIndex); + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, attribute))); + } + return true; +} + +bool TypeSystemParser::parseDefineOwnership(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument) { + m_error = u"define-ownership requires argument modification as parent"_s; + return false; + } + + TypeSystem::Language lang = TypeSystem::TargetLangCode; + std::optional<TypeSystem::Ownership> ownershipOpt; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == classAttribute) { + const auto classAttribute = attributes->takeAt(i); + const auto langOpt = languageFromAttribute(classAttribute.value()); + if (!langOpt.has_value() || langOpt.value() == TypeSystem::ShellCode) { + m_error = msgInvalidAttributeValue(classAttribute); + return false; + } + lang = langOpt.value(); + } else if (name == ownershipAttribute) { + const auto attribute = attributes->takeAt(i); + ownershipOpt = ownershipFromFromAttribute(attribute.value()); + if (!ownershipOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + } + } + + if (!ownershipOpt.has_value()) { + m_error = "unspecified ownership"_L1; + return false; + } + auto &lastArgMod = m_contextStack.top()->functionMods.last().argument_mods().last(); + switch (lang) { + case TypeSystem::TargetLangCode: + lastArgMod.setTargetOwnerShip(ownershipOpt.value()); + break; + case TypeSystem::NativeCode: + lastArgMod.setNativeOwnership(ownershipOpt.value()); + break; + default: + break; + } + return true; +} + +// ### fixme PySide7: remove (replaced by attribute). +bool TypeSystemParser::parseRename(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument) { + m_error = u"Argument modification parent required"_s; + return false; + } + + const auto toIndex = indexOfAttribute(*attributes, toAttribute); + if (toIndex == -1) { + m_error = msgMissingAttribute(toAttribute); + return false; + } + const QString renamed_to = attributes->takeAt(toIndex).value().toString(); + m_contextStack.top()->functionMods.last().argument_mods().last().setRenamedToName(renamed_to); + return true; +} + +bool TypeSystemParser::parseModifyField(const ConditionalStreamReader &, + QXmlStreamAttributes *attributes) +{ + FieldModification fm; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == nameAttribute) { + fm.setName(attributes->takeAt(i).value().toString()); + } else if (name == removeAttribute) { + fm.setRemoved(convertRemovalAttribute(attributes->takeAt(i).value())); + } else if (name == opaqueContainerFieldAttribute) { + fm.setOpaqueContainer(convertBoolean(attributes->takeAt(i).value(), + opaqueContainerFieldAttribute, false)); + } else if (name == readAttribute) { + fm.setReadable(convertBoolean(attributes->takeAt(i).value(), readAttribute, true)); + } else if (name == writeAttribute) { + fm.setWritable(convertBoolean(attributes->takeAt(i).value(), writeAttribute, true)); + } else if (name == renameAttribute) { + fm.setRenamedToName(attributes->takeAt(i).value().toString()); + } else if (name == snakeCaseAttribute) { + const auto attribute = attributes->takeAt(i); + const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); + if (snakeCaseOpt.has_value()) { + fm.setSnakeCase(snakeCaseOpt.value()); + } else { + qCWarning(lcShiboken, "%s", + qPrintable(msgInvalidAttributeValue(attribute))); + } + } + } + if (fm.name().isEmpty()) { + m_error = msgMissingAttribute(nameAttribute); + return false; + } + m_contextStack.top()->fieldMods << fm; + return true; +} + +static bool parseOverloadNumber(const QXmlStreamAttribute &attribute, int *overloadNumber, + QString *errorMessage) +{ + bool ok; + *overloadNumber = attribute.value().toInt(&ok); + if (!ok || *overloadNumber < 0) { + *errorMessage = msgInvalidAttributeValue(attribute); + return false; + } + return true; +} + +bool TypeSystemParser::parseAddFunction(const ConditionalStreamReader &, + StackElement topElement, + StackElement t, + QXmlStreamAttributes *attributes) +{ + const bool validParent = isComplexTypeEntry(topElement) + || topElement == StackElement::Root + || topElement == StackElement::ContainerTypeEntry; + if (!validParent) { + m_error = QString::fromLatin1("Add/Declare function requires a complex/container type or a root tag as parent" + ", was=%1").arg(tagFromElement(topElement)); + return false; + } + + FunctionModification mod; + if (!(t == StackElement::AddFunction + ? parseBasicModifyFunctionAttributes(attributes, &mod) + : parseModifyFunctionAttributes(attributes, &mod))) { + return false; + } + + QString originalSignature; + QString returnType; + bool staticFunction = false; + bool classMethod = false; + bool pythonOverride = false; + QString access; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == signatureAttribute) { + originalSignature = attributes->takeAt(i).value().toString().simplified(); + } else if (name == u"return-type") { + returnType = attributes->takeAt(i).value().toString(); + } else if (name == staticAttribute) { + staticFunction = convertBoolean(attributes->takeAt(i).value(), + staticAttribute, false); + } else if (name == classmethodAttribute) { + classMethod = convertBoolean(attributes->takeAt(i).value(), + classmethodAttribute, false); + } else if (name == accessAttribute) { + access = attributes->takeAt(i).value().toString(); + } else if (name == pythonOverrideAttribute) { + pythonOverride = convertBoolean(attributes->takeAt(i).value(), + pythonOverrideAttribute, false); + } + } + + QString signature = TypeDatabase::normalizedAddedFunctionSignature(originalSignature); + if (signature.isEmpty()) { + m_error = u"No signature for the added function"_s; + return false; + } + + QString errorString = checkSignatureError(signature, u"add-function"_s); + if (!errorString.isEmpty()) { + m_error = errorString; + return false; + } + + AddedFunctionPtr func = AddedFunction::createAddedFunction(signature, returnType, &errorString); + if (!func) { + m_error = errorString; + return false; + } + + func->setStatic(staticFunction); + func->setClassMethod(classMethod); + func->setPythonOverride(pythonOverride); + func->setTargetLangPackage(m_defaultPackage); + + // Create signature for matching modifications + signature = TypeDatabase::normalizedSignature(originalSignature); + if (!signature.contains(u'(')) + signature += u"()"_s; + m_currentSignature = signature; + + if (!access.isEmpty()) { + const auto acessOpt = addedFunctionAccessFromAttribute(access); + if (!acessOpt.has_value()) { + m_error = u"Bad access type '"_s + access + u'\''; + return false; + } + func->setAccess(acessOpt.value()); + } + func->setDeclaration(t == StackElement::DeclareFunction); + + m_contextStack.top()->addedFunctions << func; + m_contextStack.top()->addedFunctionModificationIndex = + m_contextStack.top()->functionMods.size(); + + if (!mod.setSignature(m_currentSignature, &m_error)) + return false; + mod.setOriginalSignature(originalSignature); + m_contextStack.top()->functionMods << mod; + return true; +} + +bool TypeSystemParser::parseAddPyMethodDef(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (!isComplexTypeEntry(topElement)) { + m_error = u"add-pymethoddef requires a complex type as parent, was="_s + + tagFromElement(topElement).toString(); + return false; + } + + TypeSystemPyMethodDefEntry def; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == nameAttribute) { + def.name = attributes->takeAt(i).value().toString(); + } else if (name == u"doc") { + def.doc = attributes->takeAt(i).value().toString(); + } else if (name == u"function") { + def.function = attributes->takeAt(i).value().toString(); + } else if (name == u"flags") { + auto attribute = attributes->takeAt(i); + const auto flags = attribute.value().split(u'|', Qt::SkipEmptyParts); + for (const auto &flag : flags) + def.methFlags.append(flag.toString().toUtf8()); + } else if (name == u"signatures") { + auto attribute = attributes->takeAt(i); + const auto signatures = attribute.value().split(u';', Qt::SkipEmptyParts); + for (const auto &signature : signatures) + def.signatures.append(signature.toString()); + } + } + + if (def.name.isEmpty() || def.function.isEmpty()) { + m_error = u"add-pymethoddef requires at least a name and a function attribute"_s; + return false; + } + std::static_pointer_cast<ComplexTypeEntry>(m_contextStack.top()->entry)->addPyMethodDef(def); + return true; +} + +bool TypeSystemParser::parseProperty(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (!isComplexTypeEntry(topElement)) { + m_error = QString::fromLatin1("Add property requires a complex type as parent" + ", was=%1").arg(tagFromElement(topElement)); + return false; + } + + TypeSystemProperty property; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == nameAttribute) { + property.name = attributes->takeAt(i).value().toString(); + } else if (name == u"get") { + property.read = attributes->takeAt(i).value().toString(); + } else if (name == u"type") { + property.type = attributes->takeAt(i).value().toString(); + } else if (name == u"set") { + property.write = attributes->takeAt(i).value().toString(); + } else if (name == generateGetSetDefAttribute) { + property.generateGetSetDef = + convertBoolean(attributes->takeAt(i).value(), + generateGetSetDefAttribute, false); + } + } + if (!property.isValid()) { + m_error = u"<property> element is missing required attibutes (name/type/get)."_s; + return false; + } + std::static_pointer_cast<ComplexTypeEntry>(m_contextStack.top()->entry)->addProperty(property); + return true; +} + +// Parse basic attributes applicable to <add-function>/<declare-function>/<function> +// and <modify-function> (all that is not done by injected code). +bool TypeSystemParser::parseBasicModifyFunctionAttributes(QXmlStreamAttributes *attributes, + FunctionModification *mod) +{ + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == overloadNumberAttribute) { + int overloadNumber = TypeSystem::OverloadNumberUnset; + if (!parseOverloadNumber(attributes->takeAt(i), &overloadNumber, &m_error)) + return false; + mod->setOverloadNumber(overloadNumber); + } + } + return true; +} + +// Parse attributes applicable to <declare-function>/<function> +// and <modify-function>. +bool TypeSystemParser::parseModifyFunctionAttributes(QXmlStreamAttributes *attributes, + FunctionModification *mod) +{ + if (!parseBasicModifyFunctionAttributes(attributes, mod)) + return false; + + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == allowThreadAttribute) { + const QXmlStreamAttribute attribute = attributes->takeAt(i); + const auto allowThreadOpt = allowThreadFromAttribute(attribute.value()); + if (!allowThreadOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + mod->setAllowThread(allowThreadOpt.value()); + } else if (name == exceptionHandlingAttribute) { + const auto attribute = attributes->takeAt(i); + const auto exceptionOpt = exceptionHandlingFromAttribute(attribute.value()); + if (!exceptionOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + mod->setExceptionHandling(exceptionOpt.value()); + } else if (name == snakeCaseAttribute) { + const auto attribute = attributes->takeAt(i); + const auto snakeCaseOpt = snakeCaseFromAttribute(attribute.value()); + if (!snakeCaseOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + mod->setSnakeCase(snakeCaseOpt.value()); + } else if (name == deprecatedAttribute) { + const bool deprecated = convertBoolean(attributes->takeAt(i).value(), + deprecatedAttribute, false); + mod->setModifierFlag(deprecated ? FunctionModification::Deprecated + : FunctionModification::Undeprecated); + } + } + return true; +} + +bool TypeSystemParser::parseModifyFunction(const ConditionalStreamReader &reader, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + const bool validParent = isComplexTypeEntry(topElement) + || topElement == StackElement::TypedefTypeEntry + || topElement == StackElement::FunctionTypeEntry; + if (!validParent) { + m_error = QString::fromLatin1("Modify function requires complex type as parent" + ", was=%1").arg(tagFromElement(topElement)); + return false; + } + + QString originalSignature; + FunctionModification mod; + if (!parseModifyFunctionAttributes(attributes, &mod)) + return false; + + QString access; + bool removed = false; + QString rename; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == signatureAttribute) { + originalSignature = attributes->takeAt(i).value().toString().simplified(); + } else if (name == accessAttribute) { + access = attributes->takeAt(i).value().toString(); + } else if (name == renameAttribute) { + rename = attributes->takeAt(i).value().toString(); + } else if (name == removeAttribute) { + removed = convertRemovalAttribute(attributes->takeAt(i).value()); + } else if (name == virtualSlotAttribute || name == threadAttribute) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeWarning(reader, name))); + } + } + + // Child of global <function> + const auto &top = m_contextStack.top(); + if (originalSignature.isEmpty() && top->entry->isFunction()) { + auto f = std::static_pointer_cast<const FunctionTypeEntry>(top->entry); + originalSignature = f->signatures().value(0); + } + + const QString signature = TypeDatabase::normalizedSignature(originalSignature); + if (signature.isEmpty()) { + m_error = u"No signature for modified function"_s; + return false; + } + + QString errorString = checkSignatureError(signature, u"modify-function"_s); + if (!errorString.isEmpty()) { + m_error = errorString; + return false; + } + + if (!mod.setSignature(signature, &m_error)) + return false; + mod.setOriginalSignature(originalSignature); + m_currentSignature = signature; + + if (!access.isEmpty()) { + const auto modifierFlagOpt = modifierFromAttribute(access); + if (!modifierFlagOpt.has_value()) { + m_error = u"Bad access type '"_s + access + u'\''; + return false; + } + const FunctionModification::ModifierFlag m = modifierFlagOpt.value(); + if (m == FunctionModification::NonFinal) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeValueWarning(reader, + accessAttribute, access))); + } + mod.setModifierFlag(m); + } + + mod.setRemoved(removed); + + if (!rename.isEmpty()) { + mod.setRenamedToName(rename); + mod.setModifierFlag(FunctionModification::Rename); + } + + top->functionMods << mod; + return true; +} + +bool TypeSystemParser::parseReplaceDefaultExpression(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (!(topElement & StackElement::ModifyArgument)) { + m_error = u"Replace default expression only allowed as child of argument modification"_s; + return false; + } + const auto withIndex = indexOfAttribute(*attributes, u"with"); + if (withIndex == -1 || attributes->at(withIndex).value().isEmpty()) { + m_error = u"Default expression replaced with empty string. Use remove-default-expression instead."_s; + return false; + } + + m_contextStack.top()->functionMods.last().argument_mods().last().setReplacedDefaultExpression( + attributes->takeAt(withIndex).value().toString()); + return true; +} + +bool TypeSystemParser::parseReferenceCount(const ConditionalStreamReader &reader, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument) { + m_error = u"reference-count must be child of modify-argument"_s; + return false; + } + + ReferenceCount rc; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == actionAttribute) { + const QXmlStreamAttribute attribute = attributes->takeAt(i); + const auto actionOpt = referenceCountFromAttribute(attribute.value()); + if (!actionOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + rc.action = actionOpt.value(); + switch (rc.action) { + case ReferenceCount::AddAll: + case ReferenceCount::Ignore: + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedAttributeValueWarning(reader, attribute))); + break; + default: + break; + } + } else if (name == u"variable-name") { + rc.varName = attributes->takeAt(i).value().toString(); + } + } + + m_contextStack.top()->functionMods.last().argument_mods().last().addReferenceCount(rc); + return true; +} + +bool TypeSystemParser::parseParentOwner(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::ModifyArgument) { + m_error = u"parent-policy must be child of modify-argument"_s; + return false; + } + ArgumentOwner ao; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == indexAttribute) { + const QString index = attributes->takeAt(i).value().toString(); + if (!parseArgumentIndex(index, &ao.index, &m_error)) + return false; + } else if (name == actionAttribute) { + const auto action = attributes->takeAt(i); + const auto actionOpt = argumentOwnerActionFromAttribute(action.value()); + if (!actionOpt.has_value()) { + m_error = msgInvalidAttributeValue(action); + return false; + } + ao.action = actionOpt.value(); + } + } + m_contextStack.top()->functionMods.last().argument_mods().last().setOwner(ao); + return true; +} + +std::optional<TypeSystemParser::Snippet> + TypeSystemParser::readFileSnippet(QXmlStreamAttributes *attributes) +{ + Snippet result; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == fileAttribute) { + result.fileName = attributes->takeAt(i).value().toString(); + } else if (name == snippetAttribute) { + result.snippetLabel = attributes->takeAt(i).value().toString(); + } + } + if (result.fileName.isEmpty()) { + m_error = "Snippet missing file name"_L1; + return std::nullopt; + } + const QString resolved = m_context->db->modifiedTypesystemFilepath(result.fileName, + m_currentPath); + if (!QFile::exists(resolved)) { + m_error = u"File for inject code not exist: "_s + + QDir::toNativeSeparators(result.fileName); + return std::nullopt; + } + QFile codeFile(resolved); + if (!codeFile.open(QIODevice::Text | QIODevice::ReadOnly)) { + m_error = msgCannotOpenForReading(codeFile); + return std::nullopt; + } + const auto contentOptional = extractSnippet(QString::fromUtf8(codeFile.readAll()), + result.snippetLabel); + codeFile.close(); + if (!contentOptional.has_value()) { + m_error = msgCannotFindSnippet(resolved, result.snippetLabel); + return std::nullopt; + } + result.content = contentOptional.value(); + return result; +} + +bool TypeSystemParser::readCodeSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip) +{ + if (!hasFileSnippetAttributes(attributes)) + return true; // Expecting inline content. + const auto snippetOptional = readFileSnippet(attributes); + if (!snippetOptional.has_value()) + return false; + const auto snippet = snippetOptional.value(); + + QString source = snippet.fileName; + if (!snippet.snippetLabel.isEmpty()) + source += " ("_L1 + snippet.snippetLabel + u')'; + QString content; + QTextStream str(&content); + str << "// ========================================================================\n" + "// START of custom code block [file: " + << source << "]\n" << snippet.content + << "// END of custom code block [file: " << source + << "]\n// ========================================================================\n"; + snip->addCode(content); + return true; +} + +bool TypeSystemParser::parseInjectCode(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if (!isComplexTypeEntry(topElement) + && (topElement != StackElement::AddFunction) + && (topElement != StackElement::ModifyFunction) + && (topElement != StackElement::Root)) { + m_error = u"wrong parent type for code injection"_s; + return false; + } + + TypeSystem::CodeSnipPosition position = TypeSystem::CodeSnipPositionBeginning; + TypeSystem::Language lang = TypeSystem::TargetLangCode; + CodeSnip snip; + if (!readCodeSnippet(attributes, &snip)) + return false; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == classAttribute) { + const auto attribute = attributes->takeAt(i); + const auto langOpt = languageFromAttribute(attribute.value()); + if (!langOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + lang = langOpt.value(); + } else if (name == positionAttribute) { + const auto attribute = attributes->takeAt(i); + const auto positionOpt = codeSnipPositionFromAttribute(attribute.value()); + if (!positionOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + position = positionOpt.value(); + } + } + + snip.position = position; + snip.language = lang; + + switch (topElement) { + case StackElement::ModifyFunction: + case StackElement::AddFunction: { + FunctionModification &mod = m_contextStack.top()->functionMods.last(); + mod.appendSnip(snip); + if (!snip.code().isEmpty()) + mod.setModifierFlag(FunctionModification::CodeInjection); + } + break; + case StackElement::Root: + std::static_pointer_cast<TypeSystemTypeEntry>(m_contextStack.top()->entry)->addCodeSnip(snip); + break; + default: + std::static_pointer_cast<ComplexTypeEntry>(m_contextStack.top()->entry)->addCodeSnip(snip); + break; + } + return true; +} + +bool TypeSystemParser::parseInclude(const ConditionalStreamReader &, + StackElement topElement, + const TypeEntryPtr &entry, QXmlStreamAttributes *attributes) +{ + QString fileName; + Include::IncludeType location = Include::IncludePath; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == fileNameAttribute) { + fileName = attributes->takeAt(i).value().toString(); + } else if (name == locationAttribute) { + const auto attribute = attributes->takeAt(i); + const auto locationOpt = locationFromAttribute(attribute.value()); + if (!locationOpt.has_value()) { + m_error = msgInvalidAttributeValue(attribute); + return false; + } + location = locationOpt.value(); + } + } + + Include inc(location, fileName); + if (isComplexTypeEntry(topElement) + || topElement == StackElement::PrimitiveTypeEntry + || topElement == StackElement::ContainerTypeEntry + || topElement == StackElement::SmartPointerTypeEntry + || topElement == StackElement::TypedefTypeEntry) { + entry->setInclude(inc); + } else if (topElement == StackElement::ExtraIncludes) { + entry->addExtraInclude(inc); + } else { + m_error = u"Only supported parent tags are primitive-type, complex types or extra-includes"_s; + return false; + } + return true; +} + +bool TypeSystemParser::parseSystemInclude(const ConditionalStreamReader &, + QXmlStreamAttributes *attributes) +{ + const auto index = indexOfAttribute(*attributes, fileNameAttribute); + if (index == -1) { + m_error = msgMissingAttribute(fileNameAttribute); + return false; + } + TypeDatabase::instance()->addForceProcessSystemInclude(attributes->takeAt(index).value().toString()); + return true; +} + +TemplateInstance * + TypeSystemParser::parseInsertTemplate(const ConditionalStreamReader &, + StackElement topElement, + QXmlStreamAttributes *attributes) +{ + if ((topElement != StackElement::InjectCode) && + (topElement != StackElement::Template) && + (topElement != StackElement::NativeToTarget) && + (topElement != StackElement::AddConversion) && + (topElement != StackElement::ConversionRule)) { + m_error = u"Can only insert templates into code snippets, templates, "\ + "conversion-rule, native-to-target or add-conversion tags."_s; + return nullptr; + } + const auto nameIndex = indexOfAttribute(*attributes, nameAttribute); + if (nameIndex == -1) { + m_error = msgMissingAttribute(nameAttribute); + return nullptr; + } + return new TemplateInstance(attributes->takeAt(nameIndex).value().toString()); +} + +bool TypeSystemParser::parseReplace(const ConditionalStreamReader &, + StackElement topElement, QXmlStreamAttributes *attributes) +{ + if (topElement != StackElement::InsertTemplate) { + m_error = u"Can only insert replace rules into insert-template."_s; + return false; + } + QString from; + QString to; + for (auto i = attributes->size() - 1; i >= 0; --i) { + const auto name = attributes->at(i).qualifiedName(); + if (name == u"from") + from = attributes->takeAt(i).value().toString(); + else if (name == toAttribute) + to = attributes->takeAt(i).value().toString(); + } + m_templateInstance->addReplaceRule(from, to); + return true; +} + +// Check for a duplicated type entry and return whether to add the new one. +// We need to be able to have duplicate primitive type entries, +// or it's not possible to cover all primitive target language +// types (which we need to do in order to support fake meta objects) +bool TypeSystemParser::checkDuplicatedTypeEntry(const ConditionalStreamReader &reader, + StackElement t, + const QString &name) const +{ + if (t == StackElement::PrimitiveTypeEntry || t == StackElement::FunctionTypeEntry) + return true; + const auto duplicated = m_context->db->findType(name); + if (!duplicated || duplicated->isNamespace()) + return true; + if (duplicated->isBuiltIn()) { + qCWarning(lcShiboken, "%s", + qPrintable(msgReaderMessage(reader, "Warning", + msgDuplicateBuiltInTypeEntry(name)))); + return false; + } + qCWarning(lcShiboken, "%s", + qPrintable(msgReaderMessage(reader, "Warning", + msgDuplicateTypeEntry(name)))); + return true; +} + +static bool parseVersion(const QString &versionSpec, const QString &package, + QVersionNumber *result, QString *errorMessage) +{ + *result = QVersionNumber::fromString(versionSpec); + if (result->isNull()) { + *errorMessage = msgInvalidVersion(versionSpec, package); + return false; + } + return true; +} + +bool TypeSystemParser::startElement(const ConditionalStreamReader &reader, StackElement element) +{ + if (m_ignoreDepth) { + ++m_ignoreDepth; + return true; + } + + const auto tagName = reader.name(); + QXmlStreamAttributes attributes = reader.attributes(); + + VersionRange versionRange; + for (auto i = attributes.size() - 1; i >= 0; --i) { + const auto name = attributes.at(i).qualifiedName(); + if (name == sinceAttribute) { + if (!parseVersion(attributes.takeAt(i).value().toString(), + m_defaultPackage, &versionRange.since, &m_error)) { + return false; + } + } else if (name == untilAttribute) { + if (!parseVersion(attributes.takeAt(i).value().toString(), + m_defaultPackage, &versionRange.until, &m_error)) { + return false; + } + } + } + + if (!m_defaultPackage.isEmpty() && !versionRange.isNull()) { + auto *td = TypeDatabase::instance(); + if (!td->checkApiVersion(m_defaultPackage, versionRange)) { + ++m_ignoreDepth; + return true; + } + } + + if (element == StackElement::ImportFile) + return importFileElement(attributes); + + if (m_currentDroppedEntryDepth) { + ++m_currentDroppedEntryDepth; + return true; + } + + if (element == StackElement::Root && m_generate == TypeEntry::GenerateCode) + customConversionsForReview.clear(); + + if (element == StackElement::Unimplemented) { + qCWarning(lcShiboken, "%s", + qPrintable(msgUnimplementedElementWarning(reader, tagName))); + return true; + } + + if (isTypeEntry(element) || element == StackElement::Root) + m_contextStack.push(std::make_shared<StackElementContext>()); + + if (m_contextStack.isEmpty()) { + m_error = msgNoRootTypeSystemEntry(); + return false; + } + + const auto &top = m_contextStack.top(); + const StackElement topElement = m_stack.value(m_stack.size() - 2, StackElement::None); + + if (isTypeEntry(element)) { + QString name; + if (element != StackElement::FunctionTypeEntry) { + const auto nameIndex = indexOfAttribute(attributes, nameAttribute); + if (nameIndex != -1) { + name = attributes.takeAt(nameIndex).value().toString(); + } else if (element != StackElement::EnumTypeEntry) { // anonymous enum? + m_error = msgMissingAttribute(nameAttribute); + return false; + } + } + // Allow for primitive and/or std:: types only, else require proper nesting. + if (element != StackElement::PrimitiveTypeEntry && name.contains(u':') + && !name.contains(u"std::")) { + m_error = msgIncorrectlyNestedName(name); + return false; + } + + if (m_context->db->hasDroppedTypeEntries()) { + const QString identifier = element == StackElement::FunctionTypeEntry + ? attributes.value(signatureAttribute).toString().simplified() : name; + if (shouldDropTypeEntry(m_context->db, m_contextStack, identifier)) { + m_currentDroppedEntryDepth = 1; + if (ReportHandler::isDebug(ReportHandler::SparseDebug)) { + qCInfo(lcShiboken, "Type system entry '%s' was intentionally dropped from generation.", + qPrintable(identifier)); + } + m_contextStack.pop(); + return true; + } + } + + // The top level tag 'function' has only the 'signature' tag + // and we should extract the 'name' value from it. + if (element == StackElement::FunctionTypeEntry + && !parseRenameFunction(reader, &name, &attributes)) { + return false; + } + + // We need to be able to have duplicate primitive type entries, + // or it's not possible to cover all primitive target language + // types (which we need to do in order to support fake meta objects) + if (element != StackElement::PrimitiveTypeEntry + && element != StackElement::FunctionTypeEntry) { + TypeEntryPtr tmp = m_context->db->findType(name); + if (tmp && !tmp->isNamespace()) + qCWarning(lcShiboken).noquote().nospace() + << "Duplicate type entry: '" << name << '\''; + } + + if (element == StackElement::EnumTypeEntry) { + const auto enumIdentifiedByIndex = + indexOfAttribute(attributes, enumIdentifiedByValueAttribute); + const QString identifiedByValue = enumIdentifiedByIndex != -1 + ? attributes.takeAt(enumIdentifiedByIndex).value().toString() : QString(); + if (name.isEmpty()) { + name = identifiedByValue; + } else if (!identifiedByValue.isEmpty()) { + m_error = u"can't specify both 'name' and 'identified-by-value' attributes"_s; + return false; + } + } + + if (name.isEmpty()) { + m_error = u"no 'name' attribute specified"_s; + return false; + } + + switch (element) { + case StackElement::CustomTypeEntry: + top->entry = parseCustomTypeEntry(reader, name, versionRange.since, &attributes); + if (Q_UNLIKELY(!top->entry)) + return false; + break; + case StackElement::PrimitiveTypeEntry: + top->entry = parsePrimitiveTypeEntry(reader, name, versionRange.since, &attributes); + if (Q_UNLIKELY(!top->entry)) + return false; + break; + case StackElement::ContainerTypeEntry: + top->entry = parseContainerTypeEntry(reader, name, versionRange.since, &attributes); + if (top->entry == nullptr) + return false; + break; + + case StackElement::SmartPointerTypeEntry: + top->entry = parseSmartPointerEntry(reader, name, versionRange.since, &attributes); + if (top->entry == nullptr) + return false; + break; + case StackElement::EnumTypeEntry: + m_currentEnum = parseEnumTypeEntry(reader, name, versionRange.since, &attributes); + if (Q_UNLIKELY(!m_currentEnum)) + return false; + top->entry = m_currentEnum; + break; + + case StackElement::ValueTypeEntry: + top->entry = parseValueTypeEntry(reader, name, versionRange.since, &attributes); + if (top->entry == nullptr) + return false; + break; + case StackElement::NamespaceTypeEntry: + top->entry = parseNamespaceTypeEntry(reader, name, versionRange.since, &attributes); + if (top->entry == nullptr) + return false; + break; + case StackElement::ObjectTypeEntry: + case StackElement::InterfaceTypeEntry: { + if (!checkRootElement()) + return false; + auto ce = std::make_shared<ObjectTypeEntry>(name, versionRange.since, currentParentTypeEntry()); + top->entry = ce; + applyCommonAttributes(reader, top->entry, &attributes); + applyComplexTypeAttributes(reader, ce, &attributes); + } + break; + case StackElement::FunctionTypeEntry: + top->entry = parseFunctionTypeEntry(reader, name, versionRange.since, &attributes); + if (Q_UNLIKELY(!top->entry)) + return false; + break; + case StackElement::TypedefTypeEntry: + top->entry = parseTypedefEntry(reader, name, topElement, + versionRange.since, &attributes); + if (top->entry == nullptr) + return false; + break; + default: + Q_ASSERT(false); + } + + if (top->entry) { + if (checkDuplicatedTypeEntry(reader, element, top->entry->name()) + && !m_context->db->addType(top->entry, &m_error)) { + return false; + } + } else { + qCWarning(lcShiboken).noquote().nospace() + << u"Type: "_s + name + u" was rejected by typesystem"_s; + } + + } else if (element == StackElement::InjectDocumentation) { + if (!parseInjectDocumentation(reader, topElement, &attributes)) + return false; + } else if (element == StackElement::ModifyDocumentation) { + if (!parseModifyDocumentation(reader, topElement, &attributes)) + return false; + } else if (element != StackElement::None) { + bool topLevel = element == StackElement::Root + || element == StackElement::SuppressedWarning + || element == StackElement::Rejection + || element == StackElement::LoadTypesystem + || element == StackElement::InjectCode + || element == StackElement::ExtraIncludes + || element == StackElement::SystemInclude + || element == StackElement::ConversionRule + || element == StackElement::AddFunction + || element == StackElement::DeclareFunction + || element == StackElement::Template + || element == StackElement::OpaqueContainer; + + if (!topLevel && m_stack.at(m_stack.size() - 2) == StackElement::Root) { + m_error = u"Tag requires parent: '"_s + tagName.toString() + u'\''; + return false; + } + + switch (element) { + case StackElement::Root: + top->entry = parseRootElement(reader, versionRange.since, &attributes); + break; + case StackElement::LoadTypesystem: + if (!loadTypesystem(reader, &attributes)) + return false; + break; + case StackElement::RejectEnumValue: + if (!parseRejectEnumValue(reader, &attributes)) + return false; + break; + case StackElement::ReplaceType: + if (!parseReplaceArgumentType(reader, topElement, &attributes)) + return false; + break; + case StackElement::ConversionRule: + if (!TypeSystemParser::parseCustomConversion(reader, topElement, &attributes)) + return false; + break; + case StackElement::NativeToTarget: + if (!parseNativeToTarget(reader, topElement, &attributes)) + return false; + break; + case StackElement::TargetToNative: { + if (topElement != StackElement::ConversionRule) { + m_error = u"Target to Native conversions can only be specified for custom conversion rules."_s; + return false; + } + + const auto topParent = m_stack.value(m_stack.size() - 3, StackElement::None); + if (isTypeEntry(topParent)) { + const auto replaceIndex = indexOfAttribute(attributes, replaceAttribute); + const bool replace = replaceIndex == -1 + || convertBoolean(attributes.takeAt(replaceIndex).value(), + replaceAttribute, true); + auto customConversion = CustomConversion::getCustomConversion(top->entry); + if (!customConversion) { + m_error = msgMissingCustomConversion(top->entry); + return false; + } + customConversion->setReplaceOriginalTargetToNativeConversions(replace); + } + } + break; + case StackElement::AddConversion: + if (!parseAddConversion(reader, topElement, &attributes)) + return false; + break; + case StackElement::ModifyArgument: + if (!parseModifyArgument(reader, topElement, &attributes)) + return false; + break; + case StackElement::NoNullPointers: + if (!parseNoNullPointer(reader, topElement, &attributes)) + return false; + break; + case StackElement::DefineOwnership: + if (!parseDefineOwnership(reader, topElement, &attributes)) + return false; + break; + case StackElement::SuppressedWarning: { + const auto textIndex = indexOfAttribute(attributes, textAttribute); + if (textIndex == -1) { + qCWarning(lcShiboken) << "Suppressed warning with no text specified"; + } else { + const QString suppressedWarning = + attributes.takeAt(textIndex).value().toString(); + if (!m_context->db->addSuppressedWarning(suppressedWarning, + m_generate == TypeEntry::GenerateCode, + &m_error)) { + return false; + } + } + } + break; + case StackElement::Rename: + if (!parseRename(reader, topElement, &attributes)) + return false; + break; + case StackElement::RemoveArgument: + if (topElement != StackElement::ModifyArgument) { + m_error = u"Removing argument requires argument modification as parent"_s; + return false; + } + + top->functionMods.last().argument_mods().last().setRemoved(true); + break; + + case StackElement::ModifyField: + if (!parseModifyField(reader, &attributes)) + return false; + break; + case StackElement::DeclareFunction: + case StackElement::AddFunction: + if (!parseAddFunction(reader, topElement, element, &attributes)) + return false; + break; + case StackElement::AddPyMethodDef: + if (!parseAddPyMethodDef(reader, topElement, &attributes)) + return false; + break; + case StackElement::Property: + if (!parseProperty(reader, topElement, &attributes)) + return false; + break; + case StackElement::ModifyFunction: + if (!parseModifyFunction(reader, topElement, &attributes)) + return false; + break; + case StackElement::ReplaceDefaultExpression: + if (!parseReplaceDefaultExpression(reader, topElement, &attributes)) + return false; + break; + case StackElement::RemoveDefaultExpression: + top->functionMods.last().argument_mods().last().setRemovedDefaultExpression(true); + break; + case StackElement::ReferenceCount: + if (!parseReferenceCount(reader, topElement, &attributes)) + return false; + break; + case StackElement::ParentOwner: + if (!parseParentOwner(reader, topElement, &attributes)) + return false; + break; + case StackElement::Array: + if (topElement != StackElement::ModifyArgument) { + m_error = u"array must be child of modify-argument"_s; + return false; + } + top->functionMods.last().argument_mods().last().setArray(true); + break; + case StackElement::InjectCode: + if (!parseInjectCode(reader, topElement, &attributes)) + return false; + break; + case StackElement::Include: + if (!parseInclude(reader, topElement, top->entry, &attributes)) + return false; + break; + case StackElement::Rejection: + if (!addRejection(m_context->db, m_generate == TypeEntry::GenerateCode, + &attributes, &m_error)) { + return false; + } + break; + case StackElement::SystemInclude: + if (!parseSystemInclude(reader, &attributes)) + return false; + break; + case StackElement::Template: { + const auto nameIndex = indexOfAttribute(attributes, nameAttribute); + if (nameIndex == -1) { + m_error = msgMissingAttribute(nameAttribute); + return false; + } + m_templateEntry.reset(new TemplateEntry(attributes.takeAt(nameIndex).value().toString())); + } + break; + case StackElement::InsertTemplate: + m_templateInstance.reset(parseInsertTemplate(reader, topElement, &attributes)); + if (!m_templateInstance) + return false; + break; + case StackElement::Replace: + if (!parseReplace(reader, topElement, &attributes)) + return false; + break; + case StackElement::OpaqueContainer: + if (!parseOpaqueContainerElement(&attributes)) + case StackElement::Configuration: + if (!parseConfiguration(topElement, &attributes)) + return false; + break; + default: + break; // nada + } + } + + if (!attributes.isEmpty()) { + const QString message = msgUnusedAttributes(tagName, attributes); + qCWarning(lcShiboken, "%s", qPrintable(msgReaderWarning(reader, message))); + } + + return true; +} diff --git a/sources/shiboken6/ApiExtractor/typesystemparser_p.h b/sources/shiboken6/ApiExtractor/typesystemparser_p.h new file mode 100644 index 000000000..4d9d4fd92 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystemparser_p.h @@ -0,0 +1,297 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef TYPESYSTEMPARSER_H +#define TYPESYSTEMPARSER_H + +#include "typesystem.h" +#include "containertypeentry.h" +#include "typedatabase.h" +#include "typedatabase_p.h" +#include "typesystem_typedefs.h" +#include "codesnip.h" + +#include <QtCore/QStack> +#include <QtCore/QHash> +#include <QtCore/QScopedPointer> + +#include <memory> +#include <optional> + +QT_FORWARD_DECLARE_CLASS(QVersionNumber) +QT_FORWARD_DECLARE_CLASS(QXmlStreamAttributes) +QT_FORWARD_DECLARE_CLASS(QXmlStreamReader) + +class ConditionalStreamReader; + +class TypeSystemEntityResolver; +class TypeDatabase; + +class FlagsTypeEntry; +class TypeSystemTypeEntry; +class ValueTypeEntry; +class EnumTypeEntry; + +enum class ParserState; + +enum class StackElement { + None, + + // Type tags + ObjectTypeEntry, + FirstTypeEntry = ObjectTypeEntry, + ValueTypeEntry, + InterfaceTypeEntry, + NamespaceTypeEntry, + LastComplexTypeEntry = NamespaceTypeEntry, + + // Non-complex type tags + PrimitiveTypeEntry, + EnumTypeEntry, + ContainerTypeEntry, + FunctionTypeEntry, + CustomTypeEntry, + SmartPointerTypeEntry, + TypedefTypeEntry, + LastTypeEntry = TypedefTypeEntry, + + // Documentation tags + InjectDocumentation, + FirstDocumentation = InjectDocumentation, + ModifyDocumentation, + LastDocumentation = ModifyDocumentation, + + // Simple tags + ExtraIncludes, + Include, + ModifyFunction, + ModifyField, + Root, + SuppressedWarning, + Rejection, + LoadTypesystem, + RejectEnumValue, + Template, + InsertTemplate, + Replace, + AddFunction, + AddPyMethodDef, + DeclareFunction, + NativeToTarget, + TargetToNative, + AddConversion, + SystemInclude, + Property, + + // Code snip tags + InjectCode, + + // Function modifier tags + Rename, // (modify-argument) + ModifyArgument, + Thread, + + // Argument modifier tags + ConversionRule, + ReplaceType, + ReplaceDefaultExpression, + RemoveArgument, + DefineOwnership, + RemoveDefaultExpression, + NoNullPointers, + ReferenceCount, + ParentOwner, + Array, + ArgumentModifiers, + + ImportFile, + OpaqueContainer, + Configuration, + Unimplemented +}; + +inline uint64_t operator&(StackElement s1, StackElement s2) +{ + return uint64_t(s1) & uint64_t(s2); +} + +inline StackElement operator|(StackElement s1, StackElement s2) +{ + return StackElement(uint64_t(s1) | uint64_t(s2)); +} + +struct StackElementContext +{ + CodeSnipList conversionCodeSnips; + AddedFunctionList addedFunctions; + FunctionModificationList functionMods; + FieldModificationList fieldMods; + DocModificationList docModifications; + TypeEntryPtr entry; + int addedFunctionModificationIndex = -1; +}; + +class TypeSystemParser +{ +public: + Q_DISABLE_COPY_MOVE(TypeSystemParser) + + using StackElementContextPtr = std::shared_ptr<StackElementContext>; + using ContextStack = QStack<StackElementContextPtr>; + + explicit TypeSystemParser(const std::shared_ptr<TypeDatabaseParserContext> &context, + bool generate); + ~TypeSystemParser(); + + bool parse(ConditionalStreamReader &reader); + + QString errorString() const { return m_error; } + +private: + struct Snippet + { + QString content; + QString fileName; + QString snippetLabel; + }; + + bool parseXml(ConditionalStreamReader &reader); + bool setupSmartPointerInstantiations(); + bool startElement(const ConditionalStreamReader &reader, StackElement element); + SmartPointerTypeEntryPtr parseSmartPointerEntry(const ConditionalStreamReader &, + const QString &name, + const QVersionNumber &since, + QXmlStreamAttributes *attributes); + bool endElement(StackElement element); + template <class String> // QString/QStringRef + bool characters(const String &ch); + + bool importFileElement(const QXmlStreamAttributes &atts); + + TypeEntryCPtr currentParentTypeEntry() const; + bool checkRootElement(); + bool applyCommonAttributes(const ConditionalStreamReader &reader, + const TypeEntryPtr &type, + QXmlStreamAttributes *attributes); + PrimitiveTypeEntryPtr + parsePrimitiveTypeEntry(const ConditionalStreamReader &, const QString &name, + const QVersionNumber &since, QXmlStreamAttributes *); + CustomTypeEntryPtr + parseCustomTypeEntry(const ConditionalStreamReader &, const QString &name, + const QVersionNumber &since, QXmlStreamAttributes *); + bool parseOpaqueContainers(QStringView s, OpaqueContainers *result); + ContainerTypeEntryPtr + parseContainerTypeEntry(const ConditionalStreamReader &, const QString &name, + const QVersionNumber &since, QXmlStreamAttributes *); + bool parseOpaqueContainerElement(QXmlStreamAttributes *attributes); + EnumTypeEntryPtr + parseEnumTypeEntry(const ConditionalStreamReader &, const QString &name, + const QVersionNumber &since, QXmlStreamAttributes *); + FlagsTypeEntryPtr + parseFlagsEntry(const ConditionalStreamReader &, const EnumTypeEntryPtr &enumEntry, + QString flagName, const QVersionNumber &since, + QXmlStreamAttributes *); + + NamespaceTypeEntryPtr + parseNamespaceTypeEntry(const ConditionalStreamReader &, + const QString &name, const QVersionNumber &since, + QXmlStreamAttributes *attributes); + + ValueTypeEntryPtr + parseValueTypeEntry(const ConditionalStreamReader &, const QString &name, + const QVersionNumber &since, QXmlStreamAttributes *); + FunctionTypeEntryPtr + parseFunctionTypeEntry(const ConditionalStreamReader &, const QString &name, + const QVersionNumber &since, QXmlStreamAttributes *); + TypedefEntryPtr + parseTypedefEntry(const ConditionalStreamReader &, const QString &name, + StackElement topElement, + const QVersionNumber &since, QXmlStreamAttributes *); + void applyComplexTypeAttributes(const ConditionalStreamReader &, const ComplexTypeEntryPtr &ctype, + QXmlStreamAttributes *) const; + bool parseConfiguration(StackElement topElement, + QXmlStreamAttributes *attributes); + bool parseRenameFunction(const ConditionalStreamReader &, QString *name, + QXmlStreamAttributes *); + bool parseInjectDocumentation(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseModifyDocumentation(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + TypeSystemTypeEntryPtr + parseRootElement(const ConditionalStreamReader &, const QVersionNumber &since, + QXmlStreamAttributes *); + bool loadTypesystem(const ConditionalStreamReader &, QXmlStreamAttributes *); + bool parseRejectEnumValue(const ConditionalStreamReader &, QXmlStreamAttributes *); + bool parseReplaceArgumentType(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseCustomConversion(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseAddConversion(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseNativeToTarget(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *attributes); + bool parseModifyArgument(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *attributes); + bool parseNoNullPointer(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *attributes); + bool parseDefineOwnership(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseRename(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseModifyField(const ConditionalStreamReader &, QXmlStreamAttributes *); + bool parseAddFunction(const ConditionalStreamReader &, StackElement topElement, + StackElement t, QXmlStreamAttributes *); + bool parseAddPyMethodDef(const ConditionalStreamReader &, + StackElement topElement, QXmlStreamAttributes *attributes); + bool parseProperty(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseBasicModifyFunctionAttributes(QXmlStreamAttributes *, + FunctionModification *mod); + bool parseModifyFunctionAttributes(QXmlStreamAttributes *, + FunctionModification *mod); + bool parseModifyFunction(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseReplaceDefaultExpression(const ConditionalStreamReader &, + StackElement topElement, QXmlStreamAttributes *); + bool parseReferenceCount(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseParentOwner(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + std::optional<Snippet> readFileSnippet(QXmlStreamAttributes *attributes); + bool readCodeSnippet(QXmlStreamAttributes *attributes, CodeSnip *snip); + bool parseInjectCode(const ConditionalStreamReader &, StackElement topElement, QXmlStreamAttributes *); + bool parseInclude(const ConditionalStreamReader &, StackElement topElement, + const TypeEntryPtr &entry, QXmlStreamAttributes *); + bool parseSystemInclude(const ConditionalStreamReader &, QXmlStreamAttributes *); + TemplateInstance + *parseInsertTemplate(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool parseReplace(const ConditionalStreamReader &, StackElement topElement, + QXmlStreamAttributes *); + bool checkDuplicatedTypeEntry(const ConditionalStreamReader &reader, + StackElement t, const QString &name) const; + ParserState parserState(qsizetype offset = 0) const; + CodeSnipAbstract *injectCodeTarget(qsizetype offset = 0) const; + + std::shared_ptr<TypeDatabaseParserContext> m_context; + QStack<StackElement> m_stack; + int m_currentDroppedEntryDepth = 0; + int m_ignoreDepth = 0; + QString m_defaultPackage; + QString m_defaultSuperclass; + TypeSystem::ExceptionHandling m_exceptionHandling = TypeSystem::ExceptionHandling::Unspecified; + TypeSystem::AllowThread m_allowThread = TypeSystem::AllowThread::Unspecified; + QString m_error; + const TypeEntry::CodeGeneration m_generate; + + EnumTypeEntryPtr m_currentEnum; + TemplateInstancePtr m_templateInstance; + TemplateEntryPtr m_templateEntry; + ContextStack m_contextStack; + + QString m_currentSignature; + QString m_currentPath; + QString m_currentFile; + QScopedPointer<TypeSystemEntityResolver> m_entityResolver; +}; + +#endif // TYPESYSTEMPARSER_H diff --git a/sources/shiboken6/ApiExtractor/typesystemtypeentry.h b/sources/shiboken6/ApiExtractor/typesystemtypeentry.h new file mode 100644 index 000000000..9b9670696 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/typesystemtypeentry.h @@ -0,0 +1,40 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TYPESYSTEMTYPEENTRY_H +#define TYPESYSTEMTYPEENTRY_H + +#include "typesystem.h" +#include "modifications_typedefs.h" +#include "typesystem_enums.h" +#include "typesystem_typedefs.h" + +class TypeSystemTypeEntry : public TypeEntry +{ +public: + explicit TypeSystemTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + TypeEntry *clone() const override; + + TypeSystem::SnakeCase snakeCase() const; + void setSnakeCase(TypeSystem::SnakeCase sc); + + const CodeSnipList &codeSnips() const; + CodeSnipList &codeSnips(); + void addCodeSnip(const CodeSnip &codeSnip); + + QString subModuleOf() const; + void setSubModule(const QString &); + + const QString &namespaceBegin() const; + void setNamespaceBegin(const QString &n); + + const QString &namespaceEnd() const; + void setNamespaceEnd(const QString &n); + +protected: + explicit TypeSystemTypeEntry(TypeEntryPrivate *d); +}; + +#endif // TYPESYSTEMTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/usingmember.h b/sources/shiboken6/ApiExtractor/usingmember.h new file mode 100644 index 000000000..346eab13c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/usingmember.h @@ -0,0 +1,21 @@ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef USINGMEMBER_H +#define USINGMEMBER_H + +#include "abstractmetalang_typedefs.h" +#include "parser/codemodel_enums.h" + +QT_FORWARD_DECLARE_CLASS(QDebug) + +struct UsingMember // Introducing a base class member via 'using' directive +{ + QString memberName; + AbstractMetaClassCPtr baseClass; + Access access; +}; + +QDebug operator<<(QDebug debug, const UsingMember &d); + +#endif // USINGMEMBER_H diff --git a/sources/shiboken6/ApiExtractor/valuetypeentry.h b/sources/shiboken6/ApiExtractor/valuetypeentry.h new file mode 100644 index 000000000..97bc26803 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/valuetypeentry.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef VALUETYPEENTRY_H +#define VALUETYPEENTRY_H + +#include "complextypeentry.h" +#include "customconversion_typedefs.h" + +class ValueTypeEntry : public ComplexTypeEntry +{ +public: + explicit ValueTypeEntry(const QString &entryName, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + + bool hasCustomConversion() const; + void setCustomConversion(const CustomConversionPtr &customConversion); + CustomConversionPtr customConversion() const; + + // FIXME PYSIDE7: Remove + /// Set the target type conversion rule + void setTargetConversionRule(const QString &conversionRule); + + /// Returns the target type conversion rule + QString targetConversionRule() const; + + /// TODO-CONVERTER: mark as deprecated + bool hasTargetConversionRule() const; + + bool isValue() const override; + + TypeEntry *clone() const override; + +protected: + explicit ValueTypeEntry(const QString &entryName, Type t, const QVersionNumber &vr, + const TypeEntryCPtr &parent); + explicit ValueTypeEntry(ComplexTypeEntryPrivate *d); +}; + +#endif // VALUETYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/varargstypeentry.h b/sources/shiboken6/ApiExtractor/varargstypeentry.h new file mode 100644 index 000000000..b2a4f4d30 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/varargstypeentry.h @@ -0,0 +1,20 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef VARARGSTYPEENTRY_H +#define VARARGSTYPEENTRY_H + +#include "typesystem.h" + +class VarargsTypeEntry : public TypeEntry +{ +public: + VarargsTypeEntry(); + + TypeEntry *clone() const override; + +protected: + explicit VarargsTypeEntry(TypeEntryPrivate *d); +}; + +#endif // VARARGSTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/voidtypeentry.h b/sources/shiboken6/ApiExtractor/voidtypeentry.h new file mode 100644 index 000000000..372c7c01f --- /dev/null +++ b/sources/shiboken6/ApiExtractor/voidtypeentry.h @@ -0,0 +1,20 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef VOIDTYPEENTRY_H +#define VOIDTYPEENTRY_H + +#include "typesystem.h" + +class VoidTypeEntry : public TypeEntry +{ +public: + VoidTypeEntry(); + + TypeEntry *clone() const override; + +protected: + explicit VoidTypeEntry(TypeEntryPrivate *d); +}; + +#endif // VOIDTYPEENTRY_H diff --git a/sources/shiboken6/ApiExtractor/xmlutils.cpp b/sources/shiboken6/ApiExtractor/xmlutils.cpp new file mode 100644 index 000000000..ccacd4ce7 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/xmlutils.cpp @@ -0,0 +1,42 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "xmlutils.h" + +#include "xmlutils_libxslt.h" + +#include "qtcompat.h" + +using namespace Qt::StringLiterals; + +XQuery::XQuery() = default; + +XQuery::~XQuery() = default; + +QString XQuery::evaluate(QString xPathExpression, QString *errorMessage) +{ + // XQuery can't have invalid XML characters + xPathExpression.replace(u'&', u"&"_s); + xPathExpression.replace(u'<', u"<"_s); + return doEvaluate(xPathExpression, errorMessage); +} + +std::shared_ptr<XQuery> XQuery::create(const QString &focus, QString *errorMessage) +{ +#if defined(HAVE_LIBXSLT) + return libXml_createXQuery(focus, errorMessage); +#else + *errorMessage = QLatin1StringView(__FUNCTION__) + u" is not implemented."_s; + return std::shared_ptr<XQuery>(); +#endif +} + +QString xsl_transform(const QString &xml, const QString &xsl, QString *errorMessage) +{ +#if defined(HAVE_LIBXSLT) + return libXslt_transform(xml, xsl, errorMessage); +#else + *errorMessage = QLatin1StringView(__FUNCTION__) + u" is not implemented."_s; + return xml; +#endif +} diff --git a/sources/shiboken6/ApiExtractor/xmlutils.h b/sources/shiboken6/ApiExtractor/xmlutils.h new file mode 100644 index 000000000..ac23c9c9c --- /dev/null +++ b/sources/shiboken6/ApiExtractor/xmlutils.h @@ -0,0 +1,29 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef XMLUTILS_H +#define XMLUTILS_H + +#include <QtCore/QString> + +#include <memory> + +class XQuery +{ +public: + Q_DISABLE_COPY_MOVE(XQuery) + + virtual ~XQuery(); + + QString evaluate(QString xPathExpression, QString *errorMessage); + + static std::shared_ptr<XQuery> create(const QString &focus, QString *errorMessage); + +protected: + XQuery(); + + virtual QString doEvaluate(const QString &xPathExpression, QString *errorMessage) = 0; +}; + +QString xsl_transform(const QString &xml, const QString &xsl, QString *errorMessage); + +#endif // XMLUTILS_H diff --git a/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp b/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp new file mode 100644 index 000000000..5a9a26913 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/xmlutils_libxslt.cpp @@ -0,0 +1,208 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "xmlutils_libxslt.h" +#include "xmlutils.h" + +#include "qtcompat.h" + +#include <QtCore/QByteArray> +#include <QtCore/QCoreApplication> +#include <QtCore/QDir> +#include <QtCore/QFile> +#include <QtCore/QString> + +#include <libxslt/xsltutils.h> +#include <libxslt/transform.h> + +#include <libxml/xmlsave.h> +#include <libxml/xpath.h> + +#include <cstdlib> +#include <memory> + +using namespace Qt::StringLiterals; + +static void cleanup() +{ + xsltCleanupGlobals(); + xmlCleanupParser(); +} + +static void ensureInitialized() +{ + static bool initialized = false; + if (!initialized) { + initialized = true; + xmlInitParser(); + xsltInit(); + qAddPostRoutine(cleanup); + } +} + +// RAI Helpers for cleaning up libxml2/libxslt data + +struct XmlDocDeleter // for std::unique_ptr<xmlDoc> +{ + void operator()(xmlDocPtr xmlDoc) { xmlFreeDoc(xmlDoc); } +}; + +struct XmlXPathObjectDeleter +{ + void operator()(xmlXPathObjectPtr xPathObject) { xmlXPathFreeObject(xPathObject); } +}; + +struct XmlStyleSheetDeleter // for std::unique_ptr<xsltStylesheet> +{ + void operator()(xsltStylesheetPtr xslt) { xsltFreeStylesheet(xslt); } +}; + +struct XmlXPathContextDeleter +{ + void operator()(xmlXPathContextPtr xPathContext) { xmlXPathFreeContext(xPathContext); } +}; + +using XmlDocUniquePtr = std::unique_ptr<xmlDoc, XmlDocDeleter>; +using XmlPathObjectUniquePtr = std::unique_ptr<xmlXPathObject, XmlXPathObjectDeleter>; +using XmlStyleSheetUniquePtr = std::unique_ptr<xsltStylesheet, XmlStyleSheetDeleter>; +using XmlXPathContextUniquePtr = std::unique_ptr<xmlXPathContext, XmlXPathContextDeleter>; + +// Helpers for formatting nodes obtained from XPATH queries + +static int qbXmlOutputWriteCallback(void *context, const char *buffer, int len) +{ + static_cast<QByteArray *>(context)->append(buffer, len); + return len; +} + +static int qbXmlOutputCloseCallback(void * /* context */) { return 0; } + +static QByteArray formatNode(xmlNodePtr node, QString *errorMessage) +{ + QByteArray result; + xmlSaveCtxtPtr saveContext = + xmlSaveToIO(qbXmlOutputWriteCallback, qbXmlOutputCloseCallback, + &result, "UTF-8", 0); + if (!saveContext) { + *errorMessage = u"xmlSaveToIO() failed."_s; + return result; + } + const long saveResult = xmlSaveTree(saveContext, node); + xmlSaveClose(saveContext); + if (saveResult < 0) + *errorMessage = u"xmlSaveTree() failed."_s; + return result; +} + +// XPath expressions +class LibXmlXQuery : public XQuery +{ +public: + explicit LibXmlXQuery(XmlDocUniquePtr &doc, XmlXPathContextUniquePtr &xpathContext) : + m_doc(std::move(doc)), m_xpathContext(std::move(xpathContext)) + { + ensureInitialized(); + } + +protected: + QString doEvaluate(const QString &xPathExpression, QString *errorMessage) override; + +private: + XmlDocUniquePtr m_doc; + XmlXPathContextUniquePtr m_xpathContext; +}; + +QString LibXmlXQuery::doEvaluate(const QString &xPathExpression, QString *errorMessage) +{ + const QByteArray xPathExpressionB = xPathExpression.toUtf8(); + auto xPathExpressionX = reinterpret_cast<const xmlChar *>(xPathExpressionB.constData()); + + XmlPathObjectUniquePtr xPathObject(xmlXPathEvalExpression(xPathExpressionX, m_xpathContext.get())); + if (!xPathObject) { + *errorMessage = u"xmlXPathEvalExpression() failed for \""_s + xPathExpression + + u'"'; + return QString(); + } + QString result; + if (xmlNodeSetPtr nodeSet = xPathObject->nodesetval) { + for (int n = 0, count = nodeSet->nodeNr; n < count; ++n) { + auto node = nodeSet->nodeTab[n]; + if (node->type == XML_ELEMENT_NODE) { + result += QString::fromLocal8Bit(formatNode(node, errorMessage)); + if (!errorMessage->isEmpty()) + return QString(); + } + } + } + return result; +} + +std::shared_ptr<XQuery> libXml_createXQuery(const QString &focus, QString *errorMessage) +{ + XmlDocUniquePtr doc(xmlReadFile(QFile::encodeName(focus).constData(), + "utf-8", XML_PARSE_NOENT)); + if (!doc) { + *errorMessage = u"libxml2: Cannot set focus to "_s + QDir::toNativeSeparators(focus); + return {}; + } + XmlXPathContextUniquePtr xpathContext(xmlXPathNewContext(doc.get())); + if (!xpathContext) { + *errorMessage = u"libxml2: xmlXPathNewContext() failed"_s; + return {}; + } + return std::shared_ptr<XQuery>(new LibXmlXQuery(doc, xpathContext)); +} + +// XSLT transformation + +static constexpr auto xsltPrefix = R"(<?xml version="1.0" encoding="UTF-8" ?> + <xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +)"_L1; + +QString libXslt_transform(const QString &xml, QString xsl, QString *errorMessage) +{ + ensureInitialized(); + // Read XML data + if (!xsl.startsWith(u"<?xml")) { + xsl.prepend(xsltPrefix); + xsl.append(u"</xsl:transform>"_s); + } + const QByteArray xmlData = xml.toUtf8(); + + XmlDocUniquePtr xmlDoc(xmlReadMemory(xmlData.constData(), int(xmlData.size()), + "", "utf-8", XML_PARSE_NOENT)); + if (!xmlDoc) { + *errorMessage = u"xmlParseMemory() failed for XML."_s; + return xml; + } + + // Read XSL data as a XML file + const QByteArray xslData = xsl.toUtf8(); + // xsltFreeStylesheet will delete this pointer + xmlDocPtr xslDoc = xmlParseMemory(xslData.constData(), xslData.size()); + if (!xslDoc) { + *errorMessage = u"xmlParseMemory() failed for XSL \""_s + xsl + u"\"."_s; + return xml; + }; + + // Parse XSL data + XmlStyleSheetUniquePtr xslt(xsltParseStylesheetDoc(xslDoc)); + if (!xslt) { + *errorMessage = u"xsltParseStylesheetDoc() failed."_s; + return xml; + } + + // Apply XSL + XmlDocUniquePtr xslResult(xsltApplyStylesheet(xslt.get(), xmlDoc.get(), nullptr)); + xmlChar *buffer = nullptr; + int bufferSize; + QString result; + if (xsltSaveResultToString(&buffer, &bufferSize, xslResult.get(), xslt.get()) == 0) { + result = QString::fromUtf8(reinterpret_cast<char*>(buffer), bufferSize); + std::free(buffer); + } else { + *errorMessage = u"xsltSaveResultToString() failed."_s; + result = xml; + } + return result.trimmed(); +} diff --git a/sources/shiboken6/ApiExtractor/xmlutils_libxslt.h b/sources/shiboken6/ApiExtractor/xmlutils_libxslt.h new file mode 100644 index 000000000..0dd8eafcb --- /dev/null +++ b/sources/shiboken6/ApiExtractor/xmlutils_libxslt.h @@ -0,0 +1,16 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef XMLUTILS_LIBXSLT_H +#define XMLUTILS_LIBXSLT_H + +#include <QtCore/QString> + +#include <memory> + +class XQuery; + +std::shared_ptr<XQuery> libXml_createXQuery(const QString &focus, QString *errorMessage); + +QString libXslt_transform(const QString &xml, QString xsl, QString *errorMessage); + +#endif // XMLUTILS_LIBXSLT_H diff --git a/sources/shiboken6/ApiExtractor/xmlutils_qt.h b/sources/shiboken6/ApiExtractor/xmlutils_qt.h new file mode 100644 index 000000000..274827044 --- /dev/null +++ b/sources/shiboken6/ApiExtractor/xmlutils_qt.h @@ -0,0 +1,16 @@ +// Copyright (C) 2019 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +#ifndef XMLUTILS_QT_H +#define XMLUTILS_QT_H + +#include <QtCore/QString> + +#include <memory> + +class XQuery; + +std::shared_ptr<XQuery> qt_createXQuery(const QString &focus, QString *errorMessage); + +QString qt_xsl_transform(const QString &xml, QString xsl, QString *errorMessage); + +#endif // XMLUTILS_QT_H |