Skip to content

Commit 1d8cee9

Browse files
committed
[ConstraintSystem] Detect invalid initializer references early
Currently invalid initializer references are detected and diagnosed in solution application phase, but that's too late because solver wouldn't have required information while attempting to determine the best solution, which might result in viable solutions being ignored in favour of incorrect ones e.g. ```swift protocol P { init(value: Int) } class C { init(value: Int, _: String = "") {} } func make<T: P & C>(type: T.Type) -> T { return T.init(value: 0) } ``` In this example `init` on `C` would be preferred since it comes from the concrete type, but reference itself is invalid because it's an attempt to construct class object using metatype value via non-required initalizer. Situations like these should be recognized early and invalid use like in case of `C.init` should be ranked lower or diagnosed if that is the only possible solution. Resolves: rdar://problem/47787705
1 parent cf53143 commit 1d8cee9

File tree

8 files changed

+266
-29
lines changed

8 files changed

+266
-29
lines changed

include/swift/AST/Expr.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,9 @@ class alignas(8) Expr {
476476
/// is not an archetype or dependent type.
477477
bool isStaticallyDerivedMetatype(
478478
llvm::function_ref<Type(const Expr *)> getType =
479-
[](const Expr *E) -> Type { return E->getType(); }) const;
479+
[](const Expr *E) -> Type { return E->getType(); },
480+
llvm::function_ref<bool(const Expr *)> isTypeReference =
481+
[](const Expr *E) { return E->isTypeReference(); }) const;
480482

481483
/// isImplicit - Determines whether this expression was implicitly-generated,
482484
/// rather than explicitly written in the AST.

lib/AST/Expr.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,9 +474,10 @@ bool Expr::isTypeReference(
474474
}
475475

476476
bool Expr::isStaticallyDerivedMetatype(
477-
llvm::function_ref<Type(const Expr *)> getType) const {
477+
llvm::function_ref<Type(const Expr *)> getType,
478+
llvm::function_ref<bool(const Expr *)> isTypeReference) const {
478479
// The expression must first be a type reference.
479-
if (!isTypeReference(getType))
480+
if (!isTypeReference(this))
480481
return false;
481482

482483
auto type = getType(this)

lib/Sema/CSApply.cpp

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -227,13 +227,57 @@ getImplicitMemberReferenceAccessSemantics(Expr *base, VarDecl *member,
227227
return member->getAccessSemanticsFromContext(DC, isAccessOnSelf);
228228
}
229229

230+
/// This effectively duplicates logic of `Expr::isTypeReference` with
231+
/// one significant difference - support for `UnresolvedDotExpr` and
232+
/// `UnresolvedMemberExpr`. This method could be used on not yet
233+
/// fully type-checked AST.
230234
bool ConstraintSystem::isTypeReference(const Expr *E) {
231-
return E->isTypeReference([&](const Expr *E) -> Type { return getType(E); });
235+
// If the result isn't a metatype, there's nothing else to do.
236+
if (!getType(E)->is<AnyMetatypeType>())
237+
return false;
238+
239+
Expr *expr = const_cast<Expr *>(E);
240+
do {
241+
// Skip syntax.
242+
expr = expr->getSemanticsProvidingExpr();
243+
244+
// Direct reference to a type.
245+
if (auto declRef = dyn_cast<DeclRefExpr>(expr))
246+
if (isa<TypeDecl>(declRef->getDecl()))
247+
return true;
248+
249+
if (isa<TypeExpr>(expr))
250+
return true;
251+
252+
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(expr)) {
253+
return isa<TypeDecl>(findResolvedMemberRef(
254+
getConstraintLocator(UDE, ConstraintLocator::Member)));
255+
}
256+
257+
if (auto *UME = dyn_cast<UnresolvedMemberExpr>(expr)) {
258+
return isa<TypeDecl>(findResolvedMemberRef(
259+
getConstraintLocator(UME, ConstraintLocator::UnresolvedMember)));
260+
}
261+
262+
// A "." expression that refers to a member.
263+
if (auto memberRef = dyn_cast<MemberRefExpr>(expr))
264+
return isa<TypeDecl>(memberRef->getMember().getDecl());
265+
266+
// When the base of a "." expression is ignored, look at the member.
267+
if (auto ignoredDot = dyn_cast<DotSyntaxBaseIgnoredExpr>(expr)) {
268+
expr = ignoredDot->getRHS();
269+
continue;
270+
}
271+
272+
// Anything else is not statically derived.
273+
return false;
274+
} while (true);
232275
}
233276

234277
bool ConstraintSystem::isStaticallyDerivedMetatype(const Expr *E) {
235278
return E->isStaticallyDerivedMetatype(
236-
[&](const Expr *E) -> Type { return getType(E); });
279+
[&](const Expr *E) -> Type { return getType(E); },
280+
[&](const Expr *E) -> bool { return isTypeReference(E); });
237281
}
238282

239283
Type ConstraintSystem::getInstanceType(const TypeExpr *E) {
@@ -317,10 +361,6 @@ static bool isNonFinalClass(Type type) {
317361
return false;
318362
}
319363

320-
// Non-required constructors may not be not inherited. Therefore when
321-
// constructing a class object, either the metatype must be statically
322-
// derived (rather than an arbitrary value of metatype type) or the referenced
323-
// constructor must be required.
324364
static bool
325365
diagnoseInvalidDynamicConstructorReferences(ConstraintSystem &cs,
326366
Expr *base,
@@ -877,14 +917,6 @@ namespace {
877917
if (auto baseMeta = baseTy->getAs<AnyMetatypeType>()) {
878918
baseIsInstance = false;
879919
baseTy = baseMeta->getInstanceType();
880-
881-
// If the member is a constructor, verify that it can be legally
882-
// referenced from this base.
883-
if (auto ctor = dyn_cast<ConstructorDecl>(member)) {
884-
if (!diagnoseInvalidDynamicConstructorReferences(cs, base, memberLoc,
885-
ctor, SuppressDiagnostics))
886-
return nullptr;
887-
}
888920
}
889921

890922
// Build a member reference.
@@ -2638,18 +2670,6 @@ namespace {
26382670
if (dre->getDecl()->getFullName() == cs.getASTContext().Id_self) {
26392671
// We have a reference to 'self'.
26402672
diagnoseBadInitRef = false;
2641-
2642-
// Special case -- in a protocol extension initializer with a class
2643-
// constrainted Self type, 'self' has archetype type, and only
2644-
// required initializers can be called.
2645-
if (cs.getType(dre)->getRValueType()->is<ArchetypeType>()) {
2646-
if (!diagnoseInvalidDynamicConstructorReferences(cs, base,
2647-
nameLoc,
2648-
ctor,
2649-
SuppressDiagnostics))
2650-
return nullptr;
2651-
}
2652-
26532673
// Make sure the reference to 'self' occurs within an initializer.
26542674
if (!dyn_cast_or_null<ConstructorDecl>(
26552675
cs.DC->getInnermostMethodContext())) {

lib/Sema/CSFix.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "CSDiagnostics.h"
2121
#include "ConstraintLocator.h"
2222
#include "ConstraintSystem.h"
23+
#include "OverloadChoice.h"
2324
#include "swift/AST/Expr.h"
2425
#include "swift/AST/Type.h"
2526
#include "swift/AST/Types.h"
@@ -274,3 +275,30 @@ AllowInvalidPartialApplication::create(bool isWarning, ConstraintSystem &cs,
274275
return new (cs.getAllocator())
275276
AllowInvalidPartialApplication(isWarning, cs, locator);
276277
}
278+
279+
bool AllowInvalidInitRef::diagnose(Expr *root, bool asNote) const {
280+
return false;
281+
}
282+
283+
AllowInvalidInitRef *AllowInvalidInitRef::dynamicOnMetatype(
284+
ConstraintSystem &cs, Type baseTy, ConstructorDecl *init,
285+
SourceRange baseRange, ConstraintLocator *locator) {
286+
return create(RefKind::DynamicOnMetatype, cs, baseTy, init,
287+
/*isStaticallyDerived=*/false, baseRange, locator);
288+
}
289+
290+
AllowInvalidInitRef *AllowInvalidInitRef::onProtocolMetatype(
291+
ConstraintSystem &cs, Type baseTy, ConstructorDecl *init,
292+
bool isStaticallyDerived, SourceRange baseRange,
293+
ConstraintLocator *locator) {
294+
return create(RefKind::ProtocolMetatype, cs, baseTy, init,
295+
isStaticallyDerived, baseRange, locator);
296+
}
297+
298+
AllowInvalidInitRef *
299+
AllowInvalidInitRef::create(RefKind kind, ConstraintSystem &cs, Type baseTy,
300+
ConstructorDecl *init, bool isStaticallyDerived,
301+
SourceRange baseRange, ConstraintLocator *locator) {
302+
return new (cs.getAllocator()) AllowInvalidInitRef(
303+
cs, kind, baseTy, init, isStaticallyDerived, baseRange, locator);
304+
}

lib/Sema/CSFix.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class SourceManager;
3737

3838
namespace constraints {
3939

40+
class OverloadChoice;
4041
class ConstraintSystem;
4142
class ConstraintLocator;
4243
class Solution;
@@ -112,6 +113,12 @@ enum class FixKind : uint8_t {
112113
/// Allow expressions where initializer call (either `self.init` or
113114
/// `super.init`) is only partially applied.
114115
AllowInvalidPartialApplication,
116+
117+
/// Non-required constructors may not be not inherited. Therefore when
118+
/// constructing a class object, either the metatype must be statically
119+
/// derived (rather than an arbitrary value of metatype type) or the
120+
/// referenced constructor must be required.
121+
AllowInvalidInitRef,
115122
};
116123

117124
class ConstraintFix {
@@ -535,6 +542,45 @@ class AllowInvalidPartialApplication final : public ConstraintFix {
535542
ConstraintLocator *locator);
536543
};
537544

545+
class AllowInvalidInitRef final : public ConstraintFix {
546+
enum class RefKind { DynamicOnMetatype, ProtocolMetatype } Kind;
547+
548+
Type BaseType;
549+
const ConstructorDecl *Init;
550+
bool IsStaticallyDerived;
551+
SourceRange BaseRange;
552+
553+
AllowInvalidInitRef(ConstraintSystem &cs, RefKind kind, Type baseTy,
554+
ConstructorDecl *init, bool isStaticallyDerived,
555+
SourceRange baseRange, ConstraintLocator *locator)
556+
: ConstraintFix(cs, FixKind::AllowInvalidInitRef, locator), Kind(kind),
557+
BaseType(baseTy), Init(init), IsStaticallyDerived(isStaticallyDerived),
558+
BaseRange(baseRange) {}
559+
560+
public:
561+
std::string getName() const override {
562+
return "allow invalid initializer reference";
563+
}
564+
565+
bool diagnose(Expr *root, bool asNote = false) const override;
566+
567+
static AllowInvalidInitRef *
568+
dynamicOnMetatype(ConstraintSystem &cs, Type baseTy, ConstructorDecl *init,
569+
SourceRange baseRange, ConstraintLocator *locator);
570+
571+
static AllowInvalidInitRef *
572+
onProtocolMetatype(ConstraintSystem &cs, Type baseTy, ConstructorDecl *init,
573+
bool isStaticallyDerived, SourceRange baseRange,
574+
ConstraintLocator *locator);
575+
576+
private:
577+
static AllowInvalidInitRef *create(RefKind kind, ConstraintSystem &cs,
578+
Type baseTy, ConstructorDecl *init,
579+
bool isStaticallyDerived,
580+
SourceRange baseRange,
581+
ConstraintLocator *locator);
582+
};
583+
538584
} // end namespace constraints
539585
} // end namespace swift
540586

lib/Sema/CSSimplify.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5517,6 +5517,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
55175517
case FixKind::RemoveUnwrap:
55185518
case FixKind::DefineMemberBasedOnUse:
55195519
case FixKind::AllowInvalidPartialApplication:
5520+
case FixKind::AllowInvalidInitRef:
55205521
llvm_unreachable("handled elsewhere");
55215522
}
55225523

lib/Sema/ConstraintSystem.cpp

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,6 +1703,129 @@ isInvalidPartialApplication(ConstraintSystem &cs, const ValueDecl *member,
17031703
return {true, level};
17041704
}
17051705

1706+
/// Determine whether the given type refers to a non-final class (or
1707+
/// dynamic self of one).
1708+
static bool isNonFinalClass(Type type) {
1709+
if (auto dynamicSelf = type->getAs<DynamicSelfType>())
1710+
type = dynamicSelf->getSelfType();
1711+
1712+
if (auto classDecl = type->getClassOrBoundGenericClass())
1713+
return !classDecl->isFinal();
1714+
1715+
if (auto archetype = type->getAs<ArchetypeType>())
1716+
if (auto super = archetype->getSuperclass())
1717+
return isNonFinalClass(super);
1718+
1719+
if (type->isExistentialType())
1720+
return true;
1721+
1722+
return false;
1723+
}
1724+
1725+
/// Determine whether given constructor reference is valid or does it require
1726+
/// any fixes e.g. when base is a protocol metatype.
1727+
static void validateInitializerRef(ConstraintSystem &cs, ConstructorDecl *init,
1728+
ConstraintLocator *locator) {
1729+
auto *anchor = locator->getAnchor();
1730+
if (!anchor)
1731+
return;
1732+
1733+
auto getType = [&cs](const Expr *expr) -> Type {
1734+
return cs.simplifyType(cs.getType(expr))->getRValueType();
1735+
};
1736+
1737+
auto locatorEndsWith =
1738+
[](ConstraintLocator *locator,
1739+
ConstraintLocator::PathElementKind eltKind) -> bool {
1740+
auto path = locator->getPath();
1741+
return !path.empty() && path.back().getKind() == eltKind;
1742+
};
1743+
1744+
Expr *baseExpr = nullptr;
1745+
Type baseType;
1746+
1747+
// Explicit initializer reference e.g. `T.init(...)` or `T.init`.
1748+
if (auto *UDE = dyn_cast<UnresolvedDotExpr>(anchor)) {
1749+
baseExpr = UDE->getBase();
1750+
baseType = getType(baseExpr);
1751+
// Initializer call e.g. `T(...)`
1752+
} else if (auto *CE = dyn_cast<CallExpr>(anchor)) {
1753+
baseExpr = CE->getFn();
1754+
baseType = getType(baseExpr);
1755+
// Initializer reference which requires contextual base type e.g. `.init(...)`.
1756+
} else if (auto *UME = dyn_cast<UnresolvedMemberExpr>(anchor)) {
1757+
// We need to find type variable which represents contextual base.
1758+
auto *baseLocator = cs.getConstraintLocator(
1759+
UME, locatorEndsWith(locator, ConstraintLocator::ConstructorMember)
1760+
? ConstraintLocator::UnresolvedMember
1761+
: ConstraintLocator::MemberRefBase);
1762+
1763+
// FIXME: Type variables responsible for contextual base could be cached
1764+
// in the constraint system to speed up lookup.
1765+
auto result = llvm::find_if(
1766+
cs.getTypeVariables(), [&baseLocator](const TypeVariableType *typeVar) {
1767+
return typeVar->getImpl().getLocator() == baseLocator;
1768+
});
1769+
1770+
assert(result != cs.getTypeVariables().end());
1771+
baseType = cs.simplifyType(*result)->getRValueType();
1772+
// Constraint for member base is formed as '$T.Type[.<member] = ...`
1773+
// which means MetatypeType has to be added after finding a type variable.
1774+
if (locatorEndsWith(baseLocator, ConstraintLocator::MemberRefBase))
1775+
baseType = MetatypeType::get(baseType);
1776+
}
1777+
1778+
if (!baseType)
1779+
return;
1780+
1781+
if (!baseType->is<AnyMetatypeType>()) {
1782+
bool applicable = false;
1783+
// Special case -- in a protocol extension initializer with a class
1784+
// constrainted Self type, 'self' has archetype type, and only
1785+
// required initializers can be called.
1786+
if (baseExpr && !baseExpr->isSuperExpr()) {
1787+
auto &ctx = cs.getASTContext();
1788+
if (auto *DRE =
1789+
dyn_cast<DeclRefExpr>(baseExpr->getSemanticsProvidingExpr())) {
1790+
if (DRE->getDecl()->getFullName() == ctx.Id_self) {
1791+
if (getType(DRE)->is<ArchetypeType>())
1792+
applicable = true;
1793+
}
1794+
}
1795+
}
1796+
1797+
if (!applicable)
1798+
return;
1799+
}
1800+
1801+
auto instanceType = baseType->getMetatypeInstanceType();
1802+
bool isStaticallyDerived = true;
1803+
// If this is expression like `.init(...)` where base type is
1804+
// determined by a contextual type.
1805+
if (!baseExpr) {
1806+
isStaticallyDerived = !(instanceType->is<DynamicSelfType>() ||
1807+
instanceType->is<ArchetypeType>());
1808+
// Otherwise this is something like `T.init(...)`
1809+
} else {
1810+
isStaticallyDerived = cs.isStaticallyDerivedMetatype(baseExpr);
1811+
}
1812+
1813+
auto baseRange = baseExpr ? baseExpr->getSourceRange() : SourceRange();
1814+
// FIXME: The "hasClangNode" check here is a complete hack.
1815+
if (isNonFinalClass(instanceType) && !isStaticallyDerived &&
1816+
!init->hasClangNode() &&
1817+
!(init->isRequired() || init->getDeclContext()->getSelfProtocolDecl())) {
1818+
(void)cs.recordFix(AllowInvalidInitRef::dynamicOnMetatype(
1819+
cs, baseType, init, baseRange, locator));
1820+
// Constructors cannot be called on a protocol metatype, because there is no
1821+
// metatype to witness it.
1822+
} else if (baseType->is<MetatypeType>() &&
1823+
instanceType->isExistentialType()) {
1824+
(void)cs.recordFix(AllowInvalidInitRef::onProtocolMetatype(
1825+
cs, baseType, init, isStaticallyDerived, baseRange, locator));
1826+
}
1827+
}
1828+
17061829
void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
17071830
Type boundType,
17081831
OverloadChoice choice,
@@ -1955,6 +2078,8 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
19552078
boundType = boundFunctionType->withExtInfo(
19562079
boundFunctionType->getExtInfo().withThrows());
19572080
}
2081+
2082+
validateInitializerRef(*this, CD, locator);
19582083
}
19592084

19602085
// Check whether applying this overload would result in invalid

test/Constraints/rdar47787705.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// RUN: %target-swift-frontend -emit-sil -verify %s | %FileCheck %s
2+
3+
protocol P {
4+
init(value: Int)
5+
}
6+
7+
class C {
8+
init(value: Int, otherValue: String = "") {}
9+
}
10+
11+
func make<T: P & C>(type: T.Type) -> T {
12+
// CHECK: [[INIT:.*]] = witness_method $T, #P.init
13+
return T.init(value: 0)
14+
}

0 commit comments

Comments
 (0)