Skip to content

Commit 462cb1f

Browse files
authored
Merge pull request #22124 from xedin/diagnose-partial-applies
[CSDiagnostics] Diagnose invalid partial application
2 parents ac557c5 + 6134533 commit 462cb1f

11 files changed

+221
-94
lines changed

lib/Sema/CSDiagnostics.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,3 +1485,24 @@ bool MissingMemberFailure::diagnoseAsError() {
14851485
corrections.noteAllCandidates();
14861486
return true;
14871487
}
1488+
1489+
bool PartialApplicationFailure::diagnoseAsError() {
1490+
auto &cs = getConstraintSystem();
1491+
auto *anchor = cast<UnresolvedDotExpr>(getRawAnchor());
1492+
1493+
RefKind kind = RefKind::MutatingMethod;
1494+
1495+
// If this is initializer delegation chain, we have a tailored message.
1496+
if (getOverloadChoiceIfAvailable(cs.getConstraintLocator(
1497+
anchor, ConstraintLocator::ConstructorMember))) {
1498+
kind = anchor->getBase()->isSuperExpr() ? RefKind::SuperInit
1499+
: RefKind::SelfInit;
1500+
}
1501+
1502+
auto diagnostic = CompatibilityWarning
1503+
? diag::partial_application_of_function_invalid_swift4
1504+
: diag::partial_application_of_function_invalid;
1505+
1506+
emitDiagnostic(anchor->getNameLoc(), diagnostic, kind);
1507+
return true;
1508+
}

lib/Sema/CSDiagnostics.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,23 @@ class MissingMemberFailure final : public FailureDiagnostic {
690690
DeclName memberName);
691691
};
692692

693+
class PartialApplicationFailure final : public FailureDiagnostic {
694+
enum RefKind : unsigned {
695+
MutatingMethod,
696+
SuperInit,
697+
SelfInit,
698+
};
699+
700+
bool CompatibilityWarning;
701+
702+
public:
703+
PartialApplicationFailure(Expr *root, bool warning, ConstraintSystem &cs,
704+
ConstraintLocator *locator)
705+
: FailureDiagnostic(root, cs, locator), CompatibilityWarning(warning) {}
706+
707+
bool diagnoseAsError() override;
708+
};
709+
693710
} // end namespace constraints
694711
} // end namespace swift
695712

lib/Sema/CSFix.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,16 @@ DefineMemberBasedOnUse::create(ConstraintSystem &cs, Type baseType,
261261
return new (cs.getAllocator())
262262
DefineMemberBasedOnUse(cs, baseType, member, locator);
263263
}
264+
265+
bool AllowInvalidPartialApplication::diagnose(Expr *root, bool asNote) const {
266+
auto failure = PartialApplicationFailure(root, isWarning(),
267+
getConstraintSystem(), getLocator());
268+
return failure.diagnose(asNote);
269+
}
270+
271+
AllowInvalidPartialApplication *
272+
AllowInvalidPartialApplication::create(bool isWarning, ConstraintSystem &cs,
273+
ConstraintLocator *locator) {
274+
return new (cs.getAllocator())
275+
AllowInvalidPartialApplication(isWarning, cs, locator);
276+
}

lib/Sema/CSFix.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ enum class FixKind : uint8_t {
104104
/// fix this issue by pretending that member exists and matches
105105
/// given arguments/result types exactly.
106106
DefineMemberBasedOnUse,
107+
108+
/// Allow expressions where 'mutating' method is only partially applied,
109+
/// which means either not applied at all e.g. `Foo.bar` or only `Self`
110+
/// is applied e.g. `foo.bar` or `Foo.bar(&foo)`.
111+
///
112+
/// Allow expressions where initializer call (either `self.init` or
113+
/// `super.init`) is only partially applied.
114+
AllowInvalidPartialApplication,
107115
};
108116

109117
class ConstraintFix {
@@ -509,6 +517,24 @@ class DefineMemberBasedOnUse final : public ConstraintFix {
509517
ConstraintLocator *locator);
510518
};
511519

520+
class AllowInvalidPartialApplication final : public ConstraintFix {
521+
public:
522+
AllowInvalidPartialApplication(bool isWarning, ConstraintSystem &cs,
523+
ConstraintLocator *locator)
524+
: ConstraintFix(cs, FixKind::AllowInvalidPartialApplication, locator,
525+
isWarning) {}
526+
527+
std::string getName() const override {
528+
return "allow partially applied 'mutating' method";
529+
}
530+
531+
bool diagnose(Expr *root, bool asNote = false) const override;
532+
533+
static AllowInvalidPartialApplication *create(bool isWarning,
534+
ConstraintSystem &cs,
535+
ConstraintLocator *locator);
536+
};
537+
512538
} // end namespace constraints
513539
} // end namespace swift
514540

lib/Sema/CSGen.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1633,7 +1633,7 @@ namespace {
16331633
expr->getFunctionRefKind(),
16341634
expr->getOuterAlternatives());
16351635
}
1636-
1636+
16371637
Type visitUnresolvedSpecializeExpr(UnresolvedSpecializeExpr *expr) {
16381638
auto baseTy = CS.getType(expr->getSubExpr());
16391639

lib/Sema/CSSimplify.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5516,6 +5516,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
55165516
case FixKind::AutoClosureForwarding:
55175517
case FixKind::RemoveUnwrap:
55185518
case FixKind::DefineMemberBasedOnUse:
5519+
case FixKind::AllowInvalidPartialApplication:
55195520
llvm_unreachable("handled elsewhere");
55205521
}
55215522

lib/Sema/ConstraintSystem.cpp

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,77 @@ resolveOverloadForDeclWithSpecialTypeCheckingSemantics(ConstraintSystem &CS,
16321632
llvm_unreachable("Unhandled DeclTypeCheckingSemantics in switch.");
16331633
}
16341634

1635+
/// \returns true if given declaration is an instance method marked as
1636+
/// `mutating`, false otherwise.
1637+
bool isMutatingMethod(const ValueDecl *decl) {
1638+
if (!(decl->isInstanceMember() && isa<FuncDecl>(decl)))
1639+
return false;
1640+
return cast<FuncDecl>(decl)->isMutating();
1641+
}
1642+
1643+
static bool shouldCheckForPartialApplication(ConstraintSystem &cs,
1644+
const ValueDecl *decl,
1645+
ConstraintLocator *locator) {
1646+
auto *anchor = locator->getAnchor();
1647+
if (!(anchor && isa<UnresolvedDotExpr>(anchor)))
1648+
return false;
1649+
1650+
// FIXME(diagnostics): This check should be removed together with
1651+
// expression based diagnostics.
1652+
if (cs.TC.isExprBeingDiagnosed(anchor))
1653+
return false;
1654+
1655+
// If this is a reference to instance method marked as 'mutating'
1656+
// it should be checked for invalid partial application.
1657+
if (isMutatingMethod(decl))
1658+
return true;
1659+
1660+
// Another unsupported partial application is related
1661+
// to constructor delegation via `self.init` or `super.init`.
1662+
1663+
if (!isa<ConstructorDecl>(decl))
1664+
return false;
1665+
1666+
auto *UDE = cast<UnresolvedDotExpr>(anchor);
1667+
// This is `super.init`
1668+
if (UDE->getBase()->isSuperExpr())
1669+
return true;
1670+
1671+
// Or this might be `self.init`.
1672+
if (auto *DRE = dyn_cast<DeclRefExpr>(UDE->getBase())) {
1673+
if (auto *baseDecl = DRE->getDecl())
1674+
return baseDecl->getBaseName() == cs.getASTContext().Id_self;
1675+
}
1676+
1677+
return false;
1678+
}
1679+
1680+
/// Try to identify and fix failures related to partial function application
1681+
/// e.g. partial application of `init` or 'mutating' instance methods.
1682+
static std::pair<bool, unsigned>
1683+
isInvalidPartialApplication(ConstraintSystem &cs, const ValueDecl *member,
1684+
ConstraintLocator *locator) {
1685+
if (!shouldCheckForPartialApplication(cs, member, locator))
1686+
return {false, 0};
1687+
1688+
auto anchor = cast<UnresolvedDotExpr>(locator->getAnchor());
1689+
// If this choice is a partial application of `init` or
1690+
// `mutating` instance method we should report that it's not allowed.
1691+
auto baseTy =
1692+
cs.simplifyType(cs.getType(anchor->getBase()))->getWithoutSpecifierType();
1693+
1694+
// If base is a metatype it would be ignored (unless this is an initializer
1695+
// call), but if it is some other type it means that we have a single
1696+
// application level already.
1697+
unsigned level =
1698+
baseTy->is<MetatypeType>() && !isa<ConstructorDecl>(member) ? 0 : 1;
1699+
if (auto *call = dyn_cast_or_null<CallExpr>(cs.getParentExpr(anchor))) {
1700+
level += dyn_cast_or_null<CallExpr>(cs.getParentExpr(call)) ? 2 : 1;
1701+
}
1702+
1703+
return {true, level};
1704+
}
1705+
16351706
void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
16361707
Type boundType,
16371708
OverloadChoice choice,
@@ -1872,10 +1943,10 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
18721943
}
18731944
assert(!refType->hasTypeParameter() && "Cannot have a dependent type here");
18741945

1875-
// If we're binding to an init member, the 'throws' need to line up between
1876-
// the bound and reference types.
18771946
if (choice.isDecl()) {
18781947
auto decl = choice.getDecl();
1948+
// If we're binding to an init member, the 'throws' need to line up between
1949+
// the bound and reference types.
18791950
if (auto CD = dyn_cast<ConstructorDecl>(decl)) {
18801951
auto boundFunctionType = boundType->getAs<AnyFunctionType>();
18811952

@@ -1885,6 +1956,41 @@ void ConstraintSystem::resolveOverload(ConstraintLocator *locator,
18851956
boundFunctionType->getExtInfo().withThrows());
18861957
}
18871958
}
1959+
1960+
// Check whether applying this overload would result in invalid
1961+
// partial function application e.g. partial application of
1962+
// mutating method or initializer.
1963+
1964+
// This check is supposed to be performed without
1965+
// `shouldAttemptFixes` because name lookup can't
1966+
// detect that particular partial application is
1967+
// invalid, so it has to return all of the candidates.
1968+
1969+
bool isInvalidPartialApply;
1970+
unsigned level;
1971+
1972+
std::tie(isInvalidPartialApply, level) =
1973+
isInvalidPartialApplication(*this, decl, locator);
1974+
1975+
if (isInvalidPartialApply) {
1976+
// No application at all e.g. `Foo.bar`.
1977+
if (level == 0) {
1978+
// Swift 4 and earlier failed to diagnose a reference to a mutating
1979+
// method without any applications at all, which would get
1980+
// miscompiled into a function with undefined behavior. Warn for
1981+
// source compatibility.
1982+
bool isWarning = !getASTContext().isSwiftVersionAtLeast(5);
1983+
(void)recordFix(
1984+
AllowInvalidPartialApplication::create(isWarning, *this, locator));
1985+
} else if (level == 1) {
1986+
// `Self` parameter is applied, e.g. `foo.bar` or `Foo.bar(&foo)`
1987+
(void)recordFix(AllowInvalidPartialApplication::create(
1988+
/*isWarning=*/false, *this, locator));
1989+
}
1990+
1991+
// Otherwise both `Self` and arguments are applied,
1992+
// e.g. `foo.bar()` or `Foo.bar(&foo)()`, and there is nothing to do.
1993+
}
18881994
}
18891995

18901996
// Note that we have resolved this overload.

lib/Sema/ConstraintSystem.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,6 +1775,12 @@ class ConstraintSystem {
17751775
ConstraintLocator *
17761776
getConstraintLocator(const ConstraintLocatorBuilder &builder);
17771777

1778+
/// Lookup and return parent associated with given expression.
1779+
Expr *getParentExpr(Expr *expr) const {
1780+
auto e = ExprWeights.find(expr);
1781+
return e != ExprWeights.end() ? e->second.second : nullptr;
1782+
}
1783+
17781784
public:
17791785

17801786
/// Whether we should attempt to fix problems.

lib/Sema/MiscDiagnostics.cpp

Lines changed: 0 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -100,92 +100,6 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
100100
unsigned level : 29;
101101
};
102102

103-
// Partial applications of functions that are not permitted. This is
104-
// tracked in post-order and unraveled as subsequent applications complete
105-
// the call (or not).
106-
llvm::SmallDenseMap<Expr*, PartialApplication,2> InvalidPartialApplications;
107-
108-
~DiagnoseWalker() override {
109-
for (auto &unapplied : InvalidPartialApplications) {
110-
unsigned kind = unapplied.second.kind;
111-
if (unapplied.second.compatibilityWarning) {
112-
TC.diagnose(unapplied.first->getLoc(),
113-
diag::partial_application_of_function_invalid_swift4,
114-
kind);
115-
} else {
116-
TC.diagnose(unapplied.first->getLoc(),
117-
diag::partial_application_of_function_invalid,
118-
kind);
119-
}
120-
}
121-
}
122-
123-
/// methods are fully applied when they can't support partial application.
124-
void checkInvalidPartialApplication(Expr *E) {
125-
if (auto AE = dyn_cast<ApplyExpr>(E)) {
126-
Expr *fnExpr = AE->getSemanticFn();
127-
if (auto forceExpr = dyn_cast<ForceValueExpr>(fnExpr))
128-
fnExpr = forceExpr->getSubExpr()->getSemanticsProvidingExpr();
129-
if (auto dotSyntaxExpr = dyn_cast<DotSyntaxBaseIgnoredExpr>(fnExpr))
130-
fnExpr = dotSyntaxExpr->getRHS();
131-
132-
// Check to see if this is a potentially unsupported partial
133-
// application of a constructor delegation.
134-
if (isa<OtherConstructorDeclRefExpr>(fnExpr)) {
135-
auto kind = AE->getArg()->isSuperExpr()
136-
? PartialApplication::SuperInit
137-
: PartialApplication::SelfInit;
138-
139-
// Partial applications of delegated initializers aren't allowed, and
140-
// don't really make sense to begin with.
141-
InvalidPartialApplications.insert(
142-
{E, {PartialApplication::Error, kind, 1}});
143-
return;
144-
}
145-
146-
// If this is adding a level to an active partial application, advance
147-
// it to the next level.
148-
auto foundApplication = InvalidPartialApplications.find(fnExpr);
149-
if (foundApplication == InvalidPartialApplications.end())
150-
return;
151-
152-
unsigned level = foundApplication->second.level;
153-
auto kind = foundApplication->second.kind;
154-
assert(level > 0);
155-
InvalidPartialApplications.erase(foundApplication);
156-
if (level > 1) {
157-
// We have remaining argument clauses.
158-
// Partial applications were always diagnosed in Swift 4 and before,
159-
// so there's no need to preserve the compatibility warning bit.
160-
InvalidPartialApplications.insert(
161-
{AE, {PartialApplication::Error, kind, level - 1}});
162-
}
163-
return;
164-
}
165-
166-
/// If this is a reference to a mutating method, it cannot be partially
167-
/// applied or even referenced without full application, so arrange for
168-
/// us to check that it gets fully applied.
169-
auto fnDeclRef = dyn_cast<DeclRefExpr>(E);
170-
if (!fnDeclRef)
171-
return;
172-
173-
auto fn = dyn_cast<FuncDecl>(fnDeclRef->getDecl());
174-
if (!fn || !fn->isInstanceMember() || !fn->isMutating())
175-
return;
176-
177-
// Swift 4 and earlier failed to diagnose a reference to a mutating method
178-
// without any applications at all, which would get miscompiled into a
179-
// function with undefined behavior. Warn for source compatibility.
180-
auto errorBehavior = TC.Context.LangOpts.isSwiftVersionAtLeast(5)
181-
? PartialApplication::Error
182-
: PartialApplication::CompatibilityWarning;
183-
184-
InvalidPartialApplications.insert(
185-
{fnDeclRef, {errorBehavior,
186-
PartialApplication::MutatingMethod, 2}});
187-
}
188-
189103
// Not interested in going outside a basic expression.
190104
std::pair<bool, Stmt *> walkToStmtPre(Stmt *S) override {
191105
return { false, S };
@@ -463,11 +377,6 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,
463377
return arg;
464378
}
465379

466-
Expr *walkToExprPost(Expr *E) override {
467-
checkInvalidPartialApplication(E);
468-
return E;
469-
}
470-
471380
void checkConvertedPointerArgument(ConcreteDeclRef callee,
472381
unsigned uncurryLevel,
473382
unsigned argIndex,

test/Constraints/mutating_members.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// RUN: %target-swift-frontend -typecheck -verify -swift-version 5 %s
22

3+
protocol P {}
4+
35
struct Foo {
46
mutating func boom() {}
57
}
@@ -20,3 +22,15 @@ func fromLocalContext2(x: inout Foo, y: Bool) -> () -> () {
2022
}
2123
}
2224

25+
func bar() -> P.Type { fatalError() }
26+
func bar() -> Foo.Type { fatalError() }
27+
28+
_ = bar().boom // expected-error{{partial application of 'mutating' method}}
29+
_ = bar().boom(&y) // expected-error{{partial application of 'mutating' method}}
30+
_ = bar().boom(&y)() // ok
31+
32+
func foo(_ foo: Foo.Type) {
33+
_ = foo.boom // expected-error{{partial application of 'mutating' method}}
34+
_ = foo.boom(&y) // expected-error{{partial application of 'mutating' method}}
35+
_ = foo.boom(&y)() // ok
36+
}

0 commit comments

Comments
 (0)