Skip to content

Commit 7bfdd4a

Browse files
committed
Provide fix-its when overriding a bridged type with the old type.
This is support for SE-0069: Mutability and Foundation Value Types. In cases where someone has overridden a method that takes, e.g. 'NSURL', the override will no longer be valid, because the system class will now use the value type 'URL' instead. If an override's full name matches exactly, the compiler will offer fix-its for any uses of reference types where value types are now preferred. (This must be a direct match; subclasses, including the mutable variants of many Foundation types, will need to be updated by hand.) One wrinkle here is the use of generics. In Swift 2, Objective-C generics weren't imported at all, so it's unlikely that the overriding method will have the correct generic arguments. Simple migration might insert the "bound" type, but it can't know what specific type might be more appropriate. Therefore, the logic to add the fix-it ignores generic arguments, assuming the parent's type is correct. rdar://problem/26183575
1 parent 84e09a8 commit 7bfdd4a

File tree

12 files changed

+322
-19
lines changed

12 files changed

+322
-19
lines changed

include/swift/AST/Decl.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4189,7 +4189,8 @@ class VarDecl : public AbstractStorageDecl {
41894189
return VarDeclBits.IsUserAccessible;
41904190
}
41914191

4192-
/// \brief Retrieve the source range of the variable type.
4192+
/// Retrieve the source range of the variable type, or an invalid range if the
4193+
/// variable's type is not explicitly written in the source.
41934194
///
41944195
/// Only for use in diagnostics. It is not always possible to always
41954196
/// precisely point to the variable type because of type aliases.

include/swift/AST/DiagnosticsSema.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,6 +1460,12 @@ NOTE(subscript_override_here,none,
14601460
"attempt to override subscript here", ())
14611461
NOTE(convenience_init_override_here,none,
14621462
"attempt to override convenience initializer here", ())
1463+
NOTE(override_type_mismatch_with_fixits,none,
1464+
"type does not match superclass %0 with type %1",
1465+
(DescriptiveDeclKind, Type))
1466+
NOTE(override_type_mismatch_with_fixits_init,none,
1467+
"type does not match superclass initializer with %select{no arguments|argument %1|arguments %1}0",
1468+
(unsigned, Type))
14631469
ERROR(override_nonclass_decl,none,
14641470
"'override' can only be specified on class members", ())
14651471
ERROR(override_property_type_mismatch,none,

lib/AST/ASTVerifier.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2769,14 +2769,6 @@ struct ASTNodeBase {};
27692769
}
27702770
checkSourceRanges(D->getSourceRange(), Parent,
27712771
[&]{ D->print(Out); });
2772-
if (auto *VD = dyn_cast<VarDecl>(D)) {
2773-
if (!VD->getTypeSourceRangeForDiagnostics().isValid()) {
2774-
Out << "invalid type source range for variable decl: ";
2775-
D->print(Out);
2776-
Out << "\n";
2777-
abort();
2778-
}
2779-
}
27802772
}
27812773

27822774
/// \brief Verify that the given source ranges is contained within the

lib/AST/Decl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3333,13 +3333,14 @@ SourceRange VarDecl::getTypeSourceRangeForDiagnostics() const {
33333333

33343334
Pattern *Pat = getParentPattern();
33353335
if (!Pat || Pat->isImplicit())
3336-
return getSourceRange();
3336+
return SourceRange();
33373337

33383338
if (auto *VP = dyn_cast<VarPattern>(Pat))
33393339
Pat = VP->getSubPattern();
33403340
if (auto *TP = dyn_cast<TypedPattern>(Pat))
33413341
return TP->getTypeLoc().getTypeRepr()->getSourceRange();
3342-
return getSourceRange();
3342+
3343+
return SourceRange();
33433344
}
33443345

33453346
static bool isVarInPattern(const VarDecl *VD, Pattern *P) {

lib/Sema/TypeCheckDecl.cpp

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4759,6 +4759,158 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
47594759
type = fnType->withExtInfo(extInfo);
47604760
}
47614761

4762+
/// Attempt to fix the type of \p decl so that it's a valid override for
4763+
/// \p base...but only if we're highly confident that we know what the user
4764+
/// should have written.
4765+
///
4766+
/// \returns true iff any fix-its were attached to \p diag.
4767+
static bool fixOverrideDeclarationTypes(InFlightDiagnostic &diag,
4768+
TypeChecker &TC,
4769+
ValueDecl *decl,
4770+
const ValueDecl *base) {
4771+
// For now, just rewrite cases where the base uses a value type and the
4772+
// override uses a reference type, and the value type is bridged to the
4773+
// reference type. This is a way to migrate code that makes use of types
4774+
// that previously were not bridged to value types.
4775+
auto checkType = [&](Type overrideTy, Type baseTy,
4776+
SourceRange typeRange) -> bool {
4777+
if (typeRange.isInvalid())
4778+
return false;
4779+
4780+
auto normalizeType = [](Type ty) -> Type {
4781+
ty = ty->getInOutObjectType();
4782+
if (Type unwrappedTy = ty->getAnyOptionalObjectType())
4783+
ty = unwrappedTy;
4784+
return ty;
4785+
};
4786+
4787+
// Is the base type bridged?
4788+
Type normalizedBaseTy = normalizeType(baseTy);
4789+
const DeclContext *DC = decl->getDeclContext();
4790+
Optional<Type> maybeBridged =
4791+
TC.Context.getBridgedToObjC(DC, normalizedBaseTy, &TC);
4792+
4793+
// ...and just knowing that it's bridged isn't good enough if we don't
4794+
// know what it's bridged /to/. Also, don't do this check for trivial
4795+
// bridging---that doesn't count.
4796+
Type bridged = maybeBridged.getValueOr(Type());
4797+
if (!bridged || bridged->isEqual(normalizedBaseTy))
4798+
return false;
4799+
4800+
// ...and is it bridged to the overridden type?
4801+
Type normalizedOverrideTy = normalizeType(overrideTy);
4802+
if (!bridged->isEqual(normalizedOverrideTy)) {
4803+
// If both are nominal types, check again, ignoring generic arguments.
4804+
auto *overrideNominal = normalizedOverrideTy->getAnyNominal();
4805+
if (!overrideNominal || bridged->getAnyNominal() != overrideNominal) {
4806+
return false;
4807+
}
4808+
}
4809+
4810+
Type newOverrideTy = baseTy;
4811+
4812+
// Preserve optionality if we're dealing with a simple type.
4813+
OptionalTypeKind OTK;
4814+
if (Type unwrappedTy = newOverrideTy->getAnyOptionalObjectType())
4815+
newOverrideTy = unwrappedTy;
4816+
if (overrideTy->getAnyOptionalObjectType(OTK))
4817+
newOverrideTy = OptionalType::get(OTK, newOverrideTy);
4818+
4819+
SmallString<32> baseTypeBuf;
4820+
llvm::raw_svector_ostream baseTypeStr(baseTypeBuf);
4821+
PrintOptions options;
4822+
options.SynthesizeSugarOnTypes = true;
4823+
4824+
newOverrideTy->print(baseTypeStr, options);
4825+
diag.fixItReplace(typeRange, baseTypeStr.str());
4826+
return true;
4827+
};
4828+
4829+
if (auto *var = dyn_cast<VarDecl>(decl)) {
4830+
SourceRange typeRange = var->getTypeSourceRangeForDiagnostics();
4831+
return checkType(var->getType(), base->getType(), typeRange);
4832+
}
4833+
4834+
if (auto *fn = dyn_cast<AbstractFunctionDecl>(decl)) {
4835+
auto *baseFn = cast<AbstractFunctionDecl>(base);
4836+
bool fixedAny = false;
4837+
if (fn->getParameterLists().back()->size() ==
4838+
baseFn->getParameterLists().back()->size()) {
4839+
for_each(*fn->getParameterLists().back(),
4840+
*baseFn->getParameterLists().back(),
4841+
[&](ParamDecl *param, const ParamDecl *baseParam) {
4842+
fixedAny |= fixOverrideDeclarationTypes(diag, TC, param, baseParam);
4843+
});
4844+
}
4845+
if (auto *method = dyn_cast<FuncDecl>(decl)) {
4846+
auto *baseMethod = cast<FuncDecl>(base);
4847+
fixedAny |= checkType(method->getBodyResultType(),
4848+
baseMethod->getBodyResultType(),
4849+
method->getBodyResultTypeLoc().getSourceRange());
4850+
}
4851+
return fixedAny;
4852+
}
4853+
4854+
if (auto *subscript = dyn_cast<SubscriptDecl>(decl)) {
4855+
auto *baseSubscript = cast<SubscriptDecl>(base);
4856+
bool fixedAny = false;
4857+
for_each(*subscript->getIndices(),
4858+
*baseSubscript->getIndices(),
4859+
[&](ParamDecl *param, const ParamDecl *baseParam) {
4860+
fixedAny |= fixOverrideDeclarationTypes(diag, TC, param, baseParam);
4861+
});
4862+
fixedAny |= checkType(subscript->getElementType(),
4863+
baseSubscript->getElementType(),
4864+
subscript->getElementTypeLoc().getSourceRange());
4865+
return fixedAny;
4866+
}
4867+
4868+
llvm_unreachable("unknown overridable member");
4869+
}
4870+
4871+
/// If the difference between the types of \p decl and \p base is something
4872+
/// we feel confident about fixing (even partially), emit a note with fix-its
4873+
/// attached. Otherwise, no note will be emitted.
4874+
///
4875+
/// \returns true iff a diagnostic was emitted.
4876+
static bool noteFixableMismatchedTypes(TypeChecker &TC, ValueDecl *decl,
4877+
const ValueDecl *base) {
4878+
DiagnosticTransaction tentativeDiags(TC.Diags);
4879+
4880+
{
4881+
Type baseTy = base->getType();
4882+
if (baseTy->is<ErrorType>())
4883+
return false;
4884+
4885+
Optional<InFlightDiagnostic> activeDiag;
4886+
if (auto *baseInit = dyn_cast<ConstructorDecl>(base)) {
4887+
// Special-case initializers, whose "type" isn't useful besides the
4888+
// input arguments.
4889+
baseTy = baseTy->getAs<AnyFunctionType>()->getResult();
4890+
Type argTy = baseTy->getAs<AnyFunctionType>()->getInput();
4891+
auto diagKind = diag::override_type_mismatch_with_fixits_init;
4892+
unsigned numArgs = baseInit->getParameters()->size();
4893+
activeDiag.emplace(TC.diagnose(decl, diagKind,
4894+
/*plural*/std::min(numArgs, 2U),
4895+
argTy));
4896+
} else {
4897+
if (isa<AbstractFunctionDecl>(base))
4898+
baseTy = baseTy->getAs<AnyFunctionType>()->getResult();
4899+
4900+
activeDiag.emplace(TC.diagnose(decl,
4901+
diag::override_type_mismatch_with_fixits,
4902+
base->getDescriptiveKind(), baseTy));
4903+
}
4904+
4905+
if (fixOverrideDeclarationTypes(*activeDiag, TC, decl, base))
4906+
return true;
4907+
}
4908+
4909+
// There weren't any fixes we knew how to make. Drop this diagnostic.
4910+
tentativeDiags.abort();
4911+
return false;
4912+
}
4913+
47624914
enum class OverrideCheckingAttempt {
47634915
PerfectMatch,
47644916
MismatchedOptional,
@@ -5132,9 +5284,21 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
51325284
// Nothing to do.
51335285

51345286
} else if (method) {
5135-
// Private migration help for overrides of Objective-C methods.
5136-
if ((!isa<FuncDecl>(method) || !cast<FuncDecl>(method)->isAccessor()) &&
5137-
(matchDecl->isObjC() || mayHaveMismatchedOptionals)) {
5287+
if (attempt == OverrideCheckingAttempt::MismatchedTypes) {
5288+
auto diagKind = diag::method_does_not_override;
5289+
if (ctor)
5290+
diagKind = diag::initializer_does_not_override;
5291+
TC.diagnose(decl, diagKind);
5292+
noteFixableMismatchedTypes(TC, decl, matchDecl);
5293+
TC.diagnose(matchDecl, diag::overridden_near_match_here,
5294+
matchDecl->getDescriptiveKind(),
5295+
matchDecl->getFullName());
5296+
emittedMatchError = true;
5297+
5298+
} else if ((!isa<FuncDecl>(method) ||
5299+
!cast<FuncDecl>(method)->isAccessor()) &&
5300+
(matchDecl->isObjC() || mayHaveMismatchedOptionals)) {
5301+
// Private migration help for overrides of Objective-C methods.
51385302
TypeLoc resultTL;
51395303
if (auto *methodAsFunc = dyn_cast<FuncDecl>(method))
51405304
resultTL = methodAsFunc->getBodyResultTypeLoc();
@@ -5156,7 +5320,15 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
51565320
return true;
51575321
}
51585322

5159-
if (mayHaveMismatchedOptionals) {
5323+
if (attempt == OverrideCheckingAttempt::MismatchedTypes) {
5324+
TC.diagnose(decl, diag::subscript_does_not_override);
5325+
noteFixableMismatchedTypes(TC, decl, matchDecl);
5326+
TC.diagnose(matchDecl, diag::overridden_near_match_here,
5327+
matchDecl->getDescriptiveKind(),
5328+
matchDecl->getFullName());
5329+
emittedMatchError = true;
5330+
5331+
} else if (mayHaveMismatchedOptionals) {
51605332
emittedMatchError |=
51615333
diagnoseMismatchedOptionals(TC, subscript,
51625334
subscript->getIndices(),
@@ -5174,6 +5346,7 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
51745346
&TC)) {
51755347
TC.diagnose(property, diag::override_property_type_mismatch,
51765348
property->getName(), propertyTy, parentPropertyTy);
5349+
noteFixableMismatchedTypes(TC, decl, matchDecl);
51775350
TC.diagnose(matchDecl, diag::property_override_here);
51785351
return true;
51795352
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// RUN: rm -rf %t && mkdir %t
2+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -emit-module -I %S/../Inputs/ObjCBridging %S/../Inputs/ObjCBridging/Appliances.swift -module-name Appliances -o %t
3+
4+
// RUN: %target-swift-frontend(mock-sdk: %clang-importer-sdk) -parse -I %S/../Inputs/ObjCBridging -I %t -parse-as-library -verify %s
5+
6+
// REQUIRES: objc_interop
7+
8+
import Appliances
9+
import Foundation
10+
11+
func checkThatBridgingIsWorking(fridge: Refrigerator) {
12+
let bridgedFridge = fridge as APPRefrigerator // no-warning
13+
_ = bridgedFridge as Refrigerator // no-warning
14+
}
15+
16+
class Base : NSObject {
17+
func test(a: Refrigerator, b: Refrigerator) -> Refrigerator? { // expected-note {{potential overridden instance method 'test(a:b:)' here}}
18+
return nil
19+
}
20+
func testGeneric(a: ManufacturerInfo<NSString>, b: ManufacturerInfo<NSString>) -> ManufacturerInfo<NSString>? { // expected-note {{potential overridden instance method 'testGeneric(a:b:)' here}} {{none}}
21+
return nil
22+
}
23+
class func testInout(_: inout Refrigerator) {} // expected-note {{potential overridden class method 'testInout' here}}
24+
func testUnmigrated(a: NSRuncingMode, b: Refrigerator, c: NSCoding) {} // expected-note {{potential overridden instance method 'testUnmigrated(a:b:c:)' here}}
25+
func testPartialMigrated(a: NSRuncingMode, b: Refrigerator) {} // expected-note {{potential overridden instance method 'testPartialMigrated(a:b:)' here}}
26+
27+
subscript(a a: Refrigerator, b b: Refrigerator) -> Refrigerator? { // expected-note {{potential overridden subscript 'subscript(a:b:)' here}} {{none}}
28+
return nil
29+
}
30+
subscript(generic a: ManufacturerInfo<NSString>, b b: ManufacturerInfo<NSString>) -> ManufacturerInfo<NSString>? { // expected-note {{potential overridden subscript 'subscript(generic:b:)' here}} {{none}}
31+
return nil
32+
}
33+
34+
init?(a: Refrigerator, b: Refrigerator) {} // expected-note {{potential overridden initializer 'init(a:b:)' here}} {{none}}
35+
init?(generic: ManufacturerInfo<NSString>, b: ManufacturerInfo<NSString>) {} // expected-note {{potential overridden initializer 'init(generic:b:)' here}} {{none}}
36+
init(singleArgument: Refrigerator) {} // expected-note {{potential overridden initializer 'init(singleArgument:)' here}} {{none}}
37+
38+
// FIXME: expected-note@+1 {{getter for 'prop' declared here}}
39+
var prop: Refrigerator // expected-note {{attempt to override property here}}
40+
// FIXME: expected-note@+1 {{getter for 'propGeneric' declared here}}
41+
var propGeneric: ManufacturerInfo<NSString> // expected-note {{attempt to override property here}}
42+
}
43+
44+
class Sub : Base {
45+
// expected-note@+1 {{type does not match superclass instance method with type '(a: Refrigerator, b: Refrigerator) -> Refrigerator?'}} {{25-40=Refrigerator}} {{45-61=Refrigerator?}} {{66-81=Refrigerator}}
46+
override func test(a: APPRefrigerator, b: APPRefrigerator?) -> APPRefrigerator { // expected-error {{method does not override any method from its superclass}} {{none}}
47+
return a
48+
}
49+
// expected-note@+1 {{type does not match superclass instance method with type '(a: ManufacturerInfo<NSString>, b: ManufacturerInfo<NSString>) -> ManufacturerInfo<NSString>?'}} {{32-62=ManufacturerInfo<NSString>}} {{67-98=ManufacturerInfo<NSString>?}} {{103-133=ManufacturerInfo<NSString>}}
50+
override func testGeneric(a: APPManufacturerInfo<AnyObject>, b: APPManufacturerInfo<AnyObject>?) -> APPManufacturerInfo<AnyObject> { // expected-error {{method does not override any method from its superclass}} {{none}}
51+
return a
52+
}
53+
// expected-note@+1 {{type does not match superclass class method with type '(inout Refrigerator) -> ()'}} {{36-57=inout Refrigerator}}
54+
override class func testInout(_: inout APPRefrigerator) {} // expected-error {{method does not override any method from its superclass}} {{none}}
55+
56+
override func testUnmigrated(a: NSObject, b: NSObject, c: NSObject) {} // expected-error {{method does not override any method from its superclass}} {{none}}
57+
58+
// expected-note@+1 {{type does not match superclass instance method with type '(a: NSRuncingMode, b: Refrigerator) -> ()'}} {{53-68=Refrigerator}}
59+
override func testPartialMigrated(a: NSObject, b: APPRefrigerator) {} // expected-error {{method does not override any method from its superclass}} {{none}}
60+
61+
// expected-note@+1 {{type does not match superclass subscript with type '(a: Refrigerator, b: Refrigerator) -> Refrigerator?'}} {{27-42=Refrigerator}} {{49-65=Refrigerator?}} {{70-85=Refrigerator}}
62+
override subscript(a a: APPRefrigerator, b b: APPRefrigerator?) -> APPRefrigerator { // expected-error {{subscript does not override any subscript from its superclass}} {{none}}
63+
return a
64+
}
65+
// expected-note@+1 {{type does not match superclass subscript with type '(generic: ManufacturerInfo<NSString>, b: ManufacturerInfo<NSString>) -> ManufacturerInfo<NSString>?'}} {{33-63=ManufacturerInfo<NSString>}} {{70-101=ManufacturerInfo<NSString>?}} {{106-136=ManufacturerInfo<NSString>}}
66+
override subscript(generic a: APPManufacturerInfo<AnyObject>, b b: APPManufacturerInfo<AnyObject>?) -> APPManufacturerInfo<AnyObject> { // expected-error {{subscript does not override any subscript from its superclass}} {{none}}
67+
return a
68+
}
69+
70+
// expected-note@+1 {{type does not match superclass initializer with arguments '(a: Refrigerator, b: Refrigerator)'}} {{20-35=Refrigerator}} {{40-56=Refrigerator?}}
71+
override init(a: APPRefrigerator, b: APPRefrigerator?) {} // expected-error {{initializer does not override a designated initializer from its superclass}}
72+
// expected-note@+1 {{type does not match superclass initializer with arguments '(generic: ManufacturerInfo<NSString>, b: ManufacturerInfo<NSString>)'}} {{28-58=ManufacturerInfo<NSString>}} {{63-94=ManufacturerInfo<NSString>?}}
73+
override init(generic a: APPManufacturerInfo<AnyObject>, b: APPManufacturerInfo<AnyObject>?) {} // expected-error {{initializer does not override a designated initializer from its superclass}}
74+
// expected-note@+1 {{type does not match superclass initializer with argument '(singleArgument: Refrigerator)'}} {{33-48=Refrigerator}}
75+
override init(singleArgument: APPRefrigerator) {} // expected-error {{initializer does not override a designated initializer from its superclass}}
76+
77+
// FIXME: expected-error@+2 {{getter for 'prop' with Objective-C selector 'prop' conflicts with getter for 'prop' from superclass 'Base' with the same Objective-C selector}}
78+
// expected-note@+1 {{type does not match superclass var with type 'Refrigerator'}} {{22-37=Refrigerator}}
79+
override var prop: APPRefrigerator { // expected-error {{property 'prop' with type 'APPRefrigerator' cannot override a property with type 'Refrigerator'}}
80+
return super.prop // implicitly bridged
81+
}
82+
// FIXME: expected-error@+2 {{getter for 'propGeneric' with Objective-C selector 'propGeneric' conflicts with getter for 'propGeneric' from superclass 'Base' with the same Objective-C selector}}
83+
// expected-note@+1 {{type does not match superclass var with type 'ManufacturerInfo<NSString>'}} {{29-59=ManufacturerInfo<NSString>}}
84+
override var propGeneric: APPManufacturerInfo<AnyObject> { // expected-error {{property 'propGeneric' with type 'APPManufacturerInfo<AnyObject>' cannot override a property with type 'ManufacturerInfo<NSString>'}}
85+
return super.prop // expected-error {{return type}}
86+
}
87+
}

test/Inputs/ObjCBridging/Appliances.apinotes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ Name: Appliances
33
Classes:
44
- Name: APPRefrigerator
55
SwiftBridge: Refrigerator
6+
- Name: APPManufacturerInfo
7+
SwiftBridge: ManufacturerInfo

test/Inputs/ObjCBridging/Appliances.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,8 @@
1212
@interface APPHouse : NSObject
1313
@property (nonatomic,nonnull,copy) APPRefrigerator *fridge;
1414
@end
15+
16+
17+
@interface APPManufacturerInfo <DataType> : NSObject
18+
@property (nonatomic,nonnull,readonly) DataType value;
19+
@end

test/Inputs/ObjCBridging/Appliances.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,7 @@ -(instancetype)init {
2222
}
2323
return self;
2424
}
25+
@end
2526

27+
@implementation APPManufacturerInfo
2628
@end

0 commit comments

Comments
 (0)