Skip to content

(WIP) Fixing up @autoclosure #10094

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -2706,9 +2706,6 @@ NOTE(silence_optional_in_interpolation_segment_call,none,
ERROR(invalid_noescape_use,none,
"non-escaping %select{value|parameter}1 %0 may only be called",
(Identifier, bool))
NOTE(noescape_autoclosure,none,
"parameter %0 is implicitly non-escaping because it was declared @autoclosure",
(Identifier))
NOTE(noescape_parameter,none,
"parameter %0 is implicitly non-escaping",
(Identifier))
Expand Down
1 change: 0 additions & 1 deletion include/swift/Migrator/FixitFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ struct FixitFilter {
Info.ID == diag::parameter_extraneous_double_up.ID ||
Info.ID == diag::attr_decl_attr_now_on_type.ID ||
Info.ID == diag::noescape_parameter.ID ||
Info.ID == diag::noescape_autoclosure.ID ||
Info.ID == diag::where_inside_brackets.ID ||
Info.ID == diag::selector_construction_suggest.ID ||
Info.ID == diag::selector_literal_deprecated_suggest.ID ||
Expand Down
14 changes: 8 additions & 6 deletions lib/Sema/CSApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7085,12 +7085,8 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
auto metaTy = cs.getType(fn)->castTo<AnyMetatypeType>();
auto ty = metaTy->getInstanceType();

// If this is an UnresolvedType in the system, preserve it.
if (ty->is<UnresolvedType>()) {
cs.setType(apply, ty);
return apply;
}

// If the metatype value isn't a type expression, the user should reference
// '.init' explicitly, for clarity.
if (!cs.isTypeReference(fn)) {
bool isExistentialType = false;
// If this is an attempt to initialize existential type.
Expand All @@ -7115,6 +7111,12 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
return coerceToType(apply->getArg(), tupleTy, locator);
}

// If this is an UnresolvedType in the system, preserve it.
if (ty->hasUnresolvedType()) {
cs.setType(apply, ty);
return apply;
}

// We're constructing a value of nominal type. Look for the constructor or
// enum element to use.
assert(ty->getNominalOrBoundGenericNominal() || ty->is<DynamicSelfType>() ||
Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/CSDiag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3846,8 +3846,7 @@ static bool tryDiagnoseNonEscapingParameterToEscaping(Expr *expr, Type srcType,

// Give a note and fixit
InFlightDiagnostic note = CS->TC.diagnose(
paramDecl->getLoc(), srcFT->isAutoClosure() ? diag::noescape_autoclosure
: diag::noescape_parameter,
paramDecl->getLoc(), diag::noescape_parameter,
paramDecl->getName());

if (!srcFT->isAutoClosure()) {
Expand Down
6 changes: 0 additions & 6 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -974,12 +974,6 @@ ConstraintSystem::SolutionKind
ConstraintSystem::matchFunctionTypes(FunctionType *func1, FunctionType *func2,
ConstraintKind kind, TypeMatchOptions flags,
ConstraintLocatorBuilder locator) {
// An @autoclosure function type can be a subtype of a
// non-@autoclosure function type.
if (func1->isAutoClosure() != func2->isAutoClosure() &&
kind < ConstraintKind::Subtype)
return SolutionKind::Error;

// A non-throwing function can be a subtype of a throwing function.
if (func1->throws() != func2->throws()) {
// Cannot drop 'throws'.
Expand Down
78 changes: 49 additions & 29 deletions lib/Sema/ConstraintSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,8 @@ Type TypeChecker::getUnopenedTypeOfReference(VarDecl *value, Type baseType,
DeclContext *UseDC,
const DeclRefExpr *base,
bool wantInterfaceType) {
assert(!baseType || wantInterfaceType);

validateDecl(value);
if (value->isInvalid())
return ErrorType::get(Context);
Expand All @@ -653,18 +655,7 @@ Type TypeChecker::getUnopenedTypeOfReference(VarDecl *value, Type baseType,
? value->getInterfaceType()
: value->getType());

requestedType = requestedType->getLValueOrInOutObjectType()
->getReferenceStorageReferent();

// If we're dealing with contextual types, and we referenced this type from
// a different context, map the type.
if (!wantInterfaceType && requestedType->hasArchetype()) {
auto valueDC = value->getDeclContext();
if (valueDC != UseDC) {
Type mapped = valueDC->mapTypeOutOfContext(requestedType);
requestedType = UseDC->mapTypeIntoContext(mapped);
}
}
requestedType = requestedType->getReferenceStorageReferent();

// Qualify storage declarations with an lvalue when appropriate.
// Otherwise, they yield rvalues (and the access must be a load).
Expand Down Expand Up @@ -773,7 +764,9 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
FunctionRefKind functionRefKind,
ConstraintLocatorBuilder locator,
const DeclRefExpr *base) {
if (value->getDeclContext()->isTypeContext() && isa<FuncDecl>(value)) {
auto valueDC = value->getDeclContext();

if (valueDC->isTypeContext() && isa<FuncDecl>(value)) {
// Unqualified lookup can find operator names within nominal types.
auto func = cast<FuncDecl>(value);
assert(func->isOperator() && "Lookup should only find operators");
Expand All @@ -785,7 +778,7 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
/*numArgumentLabelsToRemove=*/0,
locator, replacements,
func->getInnermostDeclContext(),
func->getDeclContext(),
valueDC,
/*skipProtocolSelfConstraint=*/false);
auto openedFnType = openedType->castTo<FunctionType>();

Expand All @@ -794,7 +787,7 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,

// If this is a method whose result type is dynamic Self, replace
// DynamicSelf with the actual object type.
if (!func->getDeclContext()->getAsProtocolOrProtocolExtensionContext()) {
if (!valueDC->getAsProtocolOrProtocolExtensionContext()) {
if (func->hasDynamicSelf()) {
Type selfTy = openedFnType->getInput()->getRValueInstanceType();
openedType = openedType->replaceCovariantResultType(
Expand Down Expand Up @@ -824,7 +817,7 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
functionRefKind),
locator, replacements,
funcDecl->getInnermostDeclContext(),
funcDecl->getDeclContext(),
valueDC,
/*skipProtocolSelfConstraint=*/false);

// If we opened up any type variables, record the replacements.
Expand Down Expand Up @@ -852,22 +845,33 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
return { type, type };
}

// Only remaining case: unqualified reference to a property.
auto *varDecl = cast<VarDecl>(value);
// Only remaining case: unqualified reference to a property, variable
// or function parameter.
bool wantInterfaceType = !valueDC->isLocalContext();

// Determine the type of the value, opening up that type if necessary.
bool wantInterfaceType = !varDecl->getDeclContext()->isLocalContext();
Type valueType = TC.getUnopenedTypeOfReference(varDecl, Type(), DC, base,
wantInterfaceType);
Type valueType;
if (auto *param = dyn_cast<ParamDecl>(value)) {
// Parameters are always in local context.
assert(!wantInterfaceType);
valueType = param->getType();

assert(!valueType->hasUnboundGenericType() &&
!valueType->hasTypeParameter());
// Do not propagate @autoclosure to uses of the parameter. From inside
// the function, it is just an ordinary function type.
if (auto *funcTy = valueType->getAs<FunctionType>()) {
if (funcTy->getExtInfo().isAutoClosure())
valueType = funcTy->withExtInfo(funcTy->getExtInfo()
.withIsAutoClosure(false));
}

// If this is a let-param whose type is a type variable, this is an untyped
// closure param that may be bound to an inout type later. References to the
// param should have lvalue type instead. Express the relationship with a new
// constraint.
if (auto *param = dyn_cast<ParamDecl>(varDecl)) {
// If this parameter has type 'inout T', the reference inside the function
// has type '@lvalue T'.
if (!param->isLet())
valueType = LValueType::get(valueType->getInOutObjectType());

// If this is a let-param whose type is a type variable, this is an untyped
// closure param that may be bound to an inout type later. References to the
// param should have lvalue type instead. Express the relationship with a new
// constraint.
if (param->isLet() && valueType->is<TypeVariableType>()) {
Type paramType = valueType;
valueType = createTypeVariable(getConstraintLocator(locator),
Expand All @@ -876,8 +880,24 @@ ConstraintSystem::getTypeOfReference(ValueDecl *value,
addConstraint(ConstraintKind::BindParam, paramType, valueType,
getConstraintLocator(locator));
}
} else {
auto *varDecl = cast<VarDecl>(value);
valueType = TC.getUnopenedTypeOfReference(varDecl, Type(), DC, base,
wantInterfaceType);
}

// If we're dealing with contextual types, and we referenced this type from
// a different context, map the type.
if (!wantInterfaceType && valueType->hasArchetype()) {
if (valueDC != DC) {
Type mapped = valueDC->mapTypeOutOfContext(valueType);
valueType = DC->mapTypeIntoContext(mapped);
}
}

assert(!valueType->hasUnboundGenericType() &&
!valueType->hasTypeParameter());

return { valueType, valueType };
}

Expand Down
8 changes: 2 additions & 6 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,16 +474,12 @@ static void diagSyntacticUseRestrictions(TypeChecker &TC, const Expr *E,

// If we're a parameter, emit a helpful fixit to add @escaping
auto paramDecl = dyn_cast<ParamDecl>(DRE->getDecl());
auto isAutoClosure = AFT->isAutoClosure();
if (paramDecl && !isAutoClosure) {
if (paramDecl) {
TC.diagnose(paramDecl->getStartLoc(), diag::noescape_parameter,
paramDecl->getName())
.fixItInsert(paramDecl->getTypeLoc().getSourceRange().Start,
"@escaping ");
} else if (isAutoClosure)
// TODO: add in a fixit for autoclosure
TC.diagnose(DRE->getDecl()->getLoc(), diag::noescape_autoclosure,
DRE->getDecl()->getBaseName());
}
}

// Swift 3 mode produces a warning + Fix-It for the missing ".self"
Expand Down
8 changes: 1 addition & 7 deletions lib/Sema/TypeCheckCaptures.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,17 +211,11 @@ class FindCapturedVars : public ASTWalker {

// If we're a parameter, emit a helpful fixit to add @escaping
auto paramDecl = dyn_cast<ParamDecl>(VD);
bool isAutoClosure =
VD->getInterfaceType()->castTo<AnyFunctionType>()->isAutoClosure();
if (paramDecl && !isAutoClosure) {
if (paramDecl) {
TC.diagnose(paramDecl->getStartLoc(), diag::noescape_parameter,
paramDecl->getName())
.fixItInsert(paramDecl->getTypeLoc().getSourceRange().Start,
"@escaping ");
} else if (isAutoClosure) {
// TODO: add in a fixit for autoclosure
TC.diagnose(VD->getLoc(), diag::noescape_autoclosure,
VD->getBaseName());
}
}
}
Expand Down
32 changes: 30 additions & 2 deletions test/attr/attr_autoclosure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func overloadedEach<P: P2>(_ source: P, _ closure: @escaping () -> ()) {
struct S : P2 {
typealias Element = Int
func each(_ closure: @autoclosure () -> ()) {
// expected-note@-1{{parameter 'closure' is implicitly non-escaping because it was declared @autoclosure}}
// expected-note@-1{{parameter 'closure' is implicitly non-escaping}}

overloadedEach(self, closure) // expected-error {{passing non-escaping parameter 'closure' to function expecting an @escaping closure}}
}
Expand Down Expand Up @@ -93,7 +93,7 @@ class Sub : Super {
func func12_sink(_ x: @escaping () -> Int) { }

func func12a(_ x: @autoclosure () -> Int) {
// expected-note@-1{{parameter 'x' is implicitly non-escaping because it was declared @autoclosure}}
// expected-note@-1{{parameter 'x' is implicitly non-escaping}}

func12_sink(x) // expected-error {{passing non-escaping parameter 'x' to function expecting an @escaping closure}}
}
Expand Down Expand Up @@ -157,3 +157,31 @@ func callAutoclosureWithNoEscape_3(_ fn: @autoclosure () -> Int) {
func variadicAutoclosure(_ fn: @autoclosure () -> ()...) {
for _ in fn {}
}

// rdar://problem/20591571 - Various type inference problems with @autoclosure
func id<T>(_: T) -> T {}

func same<T>(_: T, _: T) {}

func takesAnAutoclosure(_ fn: @autoclosure () -> Int, _ efn: @escaping @autoclosure () -> Int) {
// expected-note@-1 2{{parameter 'fn' is implicitly non-escaping}}

var fn2 = fn // expected-error {{non-escaping parameter 'fn' may only be called}}
// expected-warning@-1 {{initialization of variable 'fn2' was never used; consider replacing with assignment to '_' or removing it}}
let fn3 = fn // expected-error {{non-escaping parameter 'fn' may only be called}}
// expected-warning@-1 {{initialization of immutable value 'fn3' was never used; consider replacing with assignment to '_' or removing it}}

var efn2 = efn
// expected-warning@-1 {{initialization of variable 'efn2' was never used; consider replacing with assignment to '_' or removing it}}
let efn3 = efn
// expected-warning@-1 {{initialization of immutable value 'efn3' was never used; consider replacing with assignment to '_' or removing it}}

// FIXME: these should be rejected, because we shouldn't be able to
// bind a noescape function type to a generic parameter.
_ = id(fn)
_ = same(fn, { 3 })
_ = same({ 3 }, fn)

withoutActuallyEscaping(fn) { _ in }
withoutActuallyEscaping(fn) { (_: () -> Int) in }
}
2 changes: 1 addition & 1 deletion test/attr/attr_noescape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func takeNoEscapeTest2(_ fn : @noescape () -> ()) { // expected-warning{{@noesc

// Autoclosure implies noescape, but produce nice diagnostics so people know
// why noescape problems happen.
func testAutoclosure(_ a : @autoclosure () -> Int) { // expected-note{{parameter 'a' is implicitly non-escaping because it was declared @autoclosure}}
func testAutoclosure(_ a : @autoclosure () -> Int) { // expected-note{{parameter 'a' is implicitly non-escaping}}
doesEscape { a() } // expected-error {{closure use of non-escaping parameter 'a' may allow it to escape}}
}

Expand Down
26 changes: 26 additions & 0 deletions validation-test/compiler_crashers_2_fixed/0105-sr1261.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// RUN: %target-swift-frontend %s -emit-ir

class Expression<A, R> {
typealias Arg = A
typealias Ret = R
subscript(x: Arg) -> Ret! { return nil }
}
class Op<A, R> : Expression<A, R> {
typealias OpType = (Arg) -> Ret
let op: OpType
init(op: @escaping OpType) {
self.op = op
super.init()
}
}

class BinaryOp<A1, A2, R> : Op<((A1, A2)), R> {
override init(op: @escaping OpType) {
super.init(op: op)
}
override subscript(x: Arg) -> Ret! {
return op(x)
}
}
let add = BinaryOp<Int, Int, Int> { return $0.0 + $0.1 }
print(add[(1,1)])
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

// REQUIRES: asserts
// RUN: not --crash %target-swift-frontend %s -emit-ir
// RUN: not %target-swift-frontend %s -emit-ir
&{LazyFilterIndex{