Skip to content

Commit bb7d201

Browse files
committed
feat: detect types implementing SFINAE idiom
1 parent a691052 commit bb7d201

File tree

5 files changed

+328
-0
lines changed

5 files changed

+328
-0
lines changed

include/mrdocs/Config.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,10 @@ class MRDOCS_DECL
207207
*/
208208
std::vector<std::string> implementationDefined;
209209

210+
/** Whether to detect the SFINAE idiom.
211+
*/
212+
bool detectSfinae = true;
213+
210214
constexpr Settings const*
211215
operator->() const noexcept
212216
{

include/mrdocs/ConfigOptions.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ COMMON_OPTION (verbose , verbose , True if more
9999
COMMON_OPTION (report , report , The minimum reporting level: 0 to 4)
100100
COMMON_OPTION (ignoreMapErrors , ignore-map-errors , Continue if files are not mapped correctly)
101101
COMMON_OPTION (ignoreFailures , ignore-failures , Whether AST visitation failures should not stop the program)
102+
COMMON_OPTION (detectSfinae , detect-sfinae , Whether to detect the SFINAE idiom)
102103

103104
#ifdef INCLUDE_OPTION_OBJECTS
104105
#undef INCLUDE_OPTION_OBJECTS

src/lib/AST/ASTVisitor.cpp

Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,6 +1456,318 @@ class ASTVisitor
14561456
#endif
14571457
}
14581458

1459+
const NonTypeTemplateParmDecl*
1460+
getNTTPFromExpr(
1461+
const Expr* E,
1462+
unsigned Depth)
1463+
{
1464+
while(true)
1465+
{
1466+
if(const auto* ICE = dyn_cast<ImplicitCastExpr>(E))
1467+
{
1468+
E = ICE->getSubExpr();
1469+
continue;
1470+
}
1471+
if(const auto* CE = dyn_cast<ConstantExpr>(E))
1472+
{
1473+
E = CE->getSubExpr();
1474+
continue;
1475+
}
1476+
if(const auto* SNTTPE = dyn_cast<SubstNonTypeTemplateParmExpr>(E))
1477+
{
1478+
E = SNTTPE->getReplacement();
1479+
continue;
1480+
}
1481+
if(const auto* CCE = dyn_cast<CXXConstructExpr>(E);
1482+
CCE && CCE->getParenOrBraceRange().isInvalid())
1483+
{
1484+
// look through implicit copy construction from an lvalue of the same type
1485+
E = CCE->getArg(0);
1486+
continue;
1487+
}
1488+
break;
1489+
}
1490+
1491+
const auto* DRE = dyn_cast<DeclRefExpr>(E);
1492+
if(! DRE)
1493+
return nullptr;
1494+
1495+
const auto* NTTPD = dyn_cast<NonTypeTemplateParmDecl>(DRE->getDecl());
1496+
if(! NTTPD || NTTPD->getDepth() != Depth)
1497+
return nullptr;
1498+
1499+
return NTTPD;
1500+
}
1501+
1502+
std::optional<TemplateArgument>
1503+
tryGetTemplateArgument(
1504+
TemplateParameterList* Parameters,
1505+
ArrayRef<TemplateArgument> Arguments,
1506+
unsigned Index)
1507+
{
1508+
if(Index == -1)
1509+
return std::nullopt;
1510+
if(Index < Arguments.size())
1511+
return Arguments[Index];
1512+
if(Parameters && Index < Parameters->size())
1513+
{
1514+
NamedDecl* ND = Parameters->getParam(Index);
1515+
if(auto* TTPD = dyn_cast<TemplateTypeParmDecl>(ND);
1516+
TTPD && TTPD->hasDefaultArgument())
1517+
{
1518+
return TTPD->getDefaultArgument().getArgument();
1519+
}
1520+
if(auto* NTTPD = dyn_cast<NonTypeTemplateParmDecl>(ND);
1521+
NTTPD && NTTPD->hasDefaultArgument())
1522+
{
1523+
return NTTPD->getDefaultArgument().getArgument();
1524+
}
1525+
}
1526+
return std::nullopt;
1527+
}
1528+
1529+
std::optional<std::tuple<TemplateParameterList*, llvm::SmallBitVector, unsigned>>
1530+
isSFINAETemplate(
1531+
TemplateDecl* TD,
1532+
const IdentifierInfo* Member)
1533+
{
1534+
if(! TD)
1535+
return std::nullopt;
1536+
1537+
auto FindParam = [this](
1538+
ArrayRef<TemplateArgument> Arguments,
1539+
const TemplateArgument& Arg) -> unsigned
1540+
{
1541+
if(Arg.getKind() != TemplateArgument::Type)
1542+
return -1;
1543+
auto Found = std::ranges::find_if(Arguments, [&](const TemplateArgument& Other)
1544+
{
1545+
if(Other.getKind() != TemplateArgument::Type)
1546+
return false;
1547+
return context_.hasSameType(Other.getAsType(), Arg.getAsType());
1548+
});
1549+
return Found != Arguments.end() ? Found - Arguments.data() : -1;
1550+
};
1551+
1552+
if(auto* ATD = dyn_cast<TypeAliasTemplateDecl>(TD))
1553+
{
1554+
auto Underlying = ATD->getTemplatedDecl()->getUnderlyingType();
1555+
auto sfinae_info = getSFINAETemplate(Underlying, !Member);
1556+
if(! sfinae_info)
1557+
return std::nullopt;
1558+
if(Member)
1559+
sfinae_info->Member = Member;
1560+
auto sfinae_result = isSFINAETemplate(
1561+
sfinae_info->Template, sfinae_info->Member);
1562+
if(! sfinae_result)
1563+
return std::nullopt;
1564+
auto [template_params, controlling_params, param_idx] = *sfinae_result;
1565+
auto param_arg = tryGetTemplateArgument(
1566+
template_params, sfinae_info->Arguments, param_idx);
1567+
if(! param_arg)
1568+
return std::nullopt;
1569+
unsigned ParamIdx = FindParam(ATD->getInjectedTemplateArgs(), *param_arg);
1570+
return std::make_tuple(ATD->getTemplateParameters(), std::move(controlling_params), ParamIdx);
1571+
}
1572+
1573+
auto* CTD = dyn_cast<ClassTemplateDecl>(TD);
1574+
if(! CTD)
1575+
return std::nullopt;
1576+
1577+
auto PrimaryArgs = CTD->getInjectedTemplateArgs();
1578+
llvm::SmallBitVector ControllingParams(PrimaryArgs.size());
1579+
1580+
QualType MemberType;
1581+
unsigned ParamIdx = -1;
1582+
auto IsMismatch = [&](CXXRecordDecl* RD, ArrayRef<TemplateArgument> Args)
1583+
{
1584+
if(! RD->hasDefinition())
1585+
return false;
1586+
auto MemberLookup = RD->lookup(Member);
1587+
QualType CurrentType;
1588+
if(MemberLookup.empty())
1589+
{
1590+
if(! RD->getNumBases())
1591+
return false;
1592+
for(auto& Base : RD->bases())
1593+
{
1594+
auto sfinae_info = getSFINAETemplate(Base.getType(), false);
1595+
if(! sfinae_info)
1596+
{
1597+
// if the base is an opaque dependent type, we can't determine
1598+
// whether it's a SFINAE type
1599+
if(Base.getType()->isDependentType())
1600+
return true;
1601+
continue;
1602+
}
1603+
auto sfinae_result = isSFINAETemplate(
1604+
sfinae_info->Template, Member);
1605+
if(! sfinae_result)
1606+
return true;
1607+
1608+
auto [template_params, controlling_params, param_idx] = *sfinae_result;
1609+
auto param_arg = tryGetTemplateArgument(
1610+
template_params, sfinae_info->Arguments, param_idx);
1611+
if(! param_arg)
1612+
return true;
1613+
auto CurrentTypeFromBase = param_arg->getAsType();
1614+
if(CurrentType.isNull())
1615+
CurrentType = CurrentTypeFromBase;
1616+
else if(! context_.hasSameType(CurrentType, CurrentTypeFromBase))
1617+
return true;
1618+
}
1619+
// didn't find a base that defines the specified member
1620+
if(CurrentType.isNull())
1621+
return false;
1622+
}
1623+
else
1624+
{
1625+
// ambiguous lookup
1626+
if(! MemberLookup.isSingleResult())
1627+
return true;
1628+
if(auto* TND = dyn_cast<TypedefNameDecl>(MemberLookup.front()))
1629+
CurrentType = TND->getUnderlyingType();
1630+
else
1631+
// the specialization has a member with the right name,
1632+
// but it isn't an alias declaration/typedef declaration...
1633+
return true;
1634+
}
1635+
1636+
#if 0
1637+
if(! CurrentType->isDependentType())
1638+
return ! context_.hasSameType(MemberType, CurrentType);
1639+
1640+
auto FoundIdx = FindParam(Args, TemplateArgument(CurrentType));
1641+
if(ParamIdx != -1 && FoundIdx != ParamIdx)
1642+
return true;
1643+
1644+
ParamIdx = FoundIdx;
1645+
#endif
1646+
1647+
if(CurrentType->isDependentType())
1648+
{
1649+
auto FoundIdx = FindParam(Args, TemplateArgument(CurrentType));
1650+
if(FoundIdx == -1 || FoundIdx >= PrimaryArgs.size())
1651+
return true;
1652+
ParamIdx = FoundIdx;
1653+
TemplateArgument MappedPrimary = PrimaryArgs[FoundIdx];
1654+
assert(MappedPrimary.getKind() == TemplateArgument::Type);
1655+
CurrentType = MappedPrimary.getAsType();
1656+
}
1657+
1658+
if(MemberType.isNull())
1659+
MemberType = CurrentType;
1660+
1661+
return ! context_.hasSameType(MemberType, CurrentType);
1662+
};
1663+
1664+
if (IsMismatch(CTD->getTemplatedDecl(), PrimaryArgs))
1665+
return std::nullopt;
1666+
1667+
for(auto* CTSD : CTD->specializations())
1668+
{
1669+
if(CTSD->isExplicitSpecialization() &&
1670+
IsMismatch(CTSD, CTSD->getTemplateArgs().asArray()))
1671+
return std::nullopt;
1672+
}
1673+
1674+
SmallVector<ClassTemplatePartialSpecializationDecl*> PartialSpecs;
1675+
CTD->getPartialSpecializations(PartialSpecs);
1676+
1677+
for(auto* CTPSD : PartialSpecs)
1678+
{
1679+
auto PartialArgs = CTPSD->getTemplateArgs().asArray();
1680+
if(IsMismatch(CTPSD, PartialArgs))
1681+
return std::nullopt;
1682+
for(std::size_t I = 0; I < PartialArgs.size(); ++I)
1683+
{
1684+
TemplateArgument Arg = PartialArgs[I];
1685+
switch(Arg.getKind())
1686+
{
1687+
case TemplateArgument::Integral:
1688+
case TemplateArgument::Declaration:
1689+
case TemplateArgument::StructuralValue:
1690+
case TemplateArgument::NullPtr:
1691+
break;
1692+
case TemplateArgument::Expression:
1693+
if(getNTTPFromExpr(
1694+
Arg.getAsExpr(),
1695+
CTPSD->getTemplateDepth() - 1))
1696+
continue;
1697+
break;
1698+
default:
1699+
continue;
1700+
}
1701+
ControllingParams.set(I);
1702+
//.getAsExpr()
1703+
}
1704+
}
1705+
1706+
return std::make_tuple(CTD->getTemplateParameters(), std::move(ControllingParams), ParamIdx);
1707+
}
1708+
1709+
struct SFINAEInfo
1710+
{
1711+
TemplateDecl* Template = nullptr;
1712+
const IdentifierInfo* Member = nullptr;
1713+
ArrayRef<TemplateArgument> Arguments;
1714+
};
1715+
1716+
std::optional<SFINAEInfo>
1717+
getSFINAETemplate(QualType T, bool AllowDependentNames)
1718+
{
1719+
assert(!T.isNull());
1720+
SFINAEInfo SFINAE;
1721+
if(auto* ET = T->getAs<ElaboratedType>())
1722+
T = ET->getNamedType();
1723+
1724+
if(auto* DNT = T->getAsAdjusted<DependentNameType>();
1725+
DNT && AllowDependentNames)
1726+
{
1727+
SFINAE.Member = DNT->getIdentifier();
1728+
T = QualType(DNT->getQualifier()->getAsType(), 0);
1729+
}
1730+
1731+
if(auto* TST = T->getAsAdjusted<TemplateSpecializationType>())
1732+
{
1733+
SFINAE.Template = TST->getTemplateName().getAsTemplateDecl();
1734+
SFINAE.Arguments = TST->template_arguments();
1735+
return SFINAE;
1736+
}
1737+
return std::nullopt;
1738+
}
1739+
1740+
std::optional<std::pair<QualType, std::vector<TemplateArgument>>>
1741+
isSFINAEType(QualType T)
1742+
{
1743+
auto sfinae_info = getSFINAETemplate(T, true);
1744+
if(! sfinae_info)
1745+
return std::nullopt;
1746+
1747+
auto sfinae_result = isSFINAETemplate(
1748+
sfinae_info->Template, sfinae_info->Member);
1749+
1750+
if(! sfinae_result)
1751+
return std::nullopt;
1752+
1753+
auto [template_params, controlling_params, param_idx] = *sfinae_result;
1754+
1755+
auto Args = sfinae_info->Arguments;
1756+
auto param_arg = tryGetTemplateArgument(
1757+
template_params, Args, param_idx);
1758+
if(! param_arg)
1759+
return std::nullopt;
1760+
1761+
std::vector<TemplateArgument> ControllingArgs;
1762+
for(std::size_t I = 0; I < Args.size(); ++I)
1763+
{
1764+
if(controlling_params[I])
1765+
ControllingArgs.emplace_back(Args[I]);
1766+
}
1767+
1768+
return std::make_pair(param_arg->getAsType(), std::move(ControllingArgs));
1769+
}
1770+
14591771
std::string
14601772
extractName(
14611773
const NamedDecl* D)
@@ -3752,6 +4064,13 @@ buildTypeInfo(
37524064
ExtractionScope scope = enterMode(extract_mode);
37534065
// build the TypeInfo representation for the type
37544066
TypeInfoBuilder Builder(*this);
4067+
4068+
if(config_->detectSfinae)
4069+
{
4070+
if(auto SFINAE = isSFINAEType(qt); SFINAE.has_value())
4071+
qt = SFINAE->first.withFastQualifiers(qt.getLocalFastQualifiers());
4072+
}
4073+
37554074
Builder.Visit(qt);
37564075
return Builder.result();
37574076
}

src/tool/ToolArgs.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ R"(
6767
, filters(filtersCat)
6868
, seeBelow(llvm::cl::cat(filtersCat))
6969
, implementationDefined(llvm::cl::cat(filtersCat))
70+
, detectSfinae(llvm::cl::cat(extraCat))
7071
{}
7172

7273
ToolArgs::

src/tool/ToolArgs.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ class ToolArgs
144144
*/
145145
llvm::cl::list<std::string> implementationDefined;
146146

147+
/** Whether to detect the SFINAE idiom.
148+
*/
149+
llvm::cl::opt<bool> detectSfinae;
147150

148151
/// Apply the command line options to the settings
149152
Expected<void>

0 commit comments

Comments
 (0)