From b2083db45d88696eec1c34e5e79ce1f9849e290b Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 29 Jan 2020 00:06:57 -0800 Subject: [PATCH 1/2] [ConstraintSystem] Add a fix to allow conversion between non-class type and AnyObject --- lib/Sema/CSFix.cpp | 37 +++++++++++++++++++++++++++++++++++++ lib/Sema/CSFix.h | 22 ++++++++++++++++++++-- lib/Sema/CSSimplify.cpp | 13 +++++++++++-- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/lib/Sema/CSFix.cpp b/lib/Sema/CSFix.cpp index ffc4e502509a5..95e78c3a454be 100644 --- a/lib/Sema/CSFix.cpp +++ b/lib/Sema/CSFix.cpp @@ -1180,3 +1180,40 @@ SpecifyObjectLiteralTypeImport::create(ConstraintSystem &cs, ConstraintLocator *locator) { return new (cs.getAllocator()) SpecifyObjectLiteralTypeImport(cs, locator); } + +AllowNonClassTypeToConvertToAnyObject::AllowNonClassTypeToConvertToAnyObject( + ConstraintSystem &cs, Type type, ConstraintLocator *locator) + : ContextualMismatch(cs, FixKind::AllowNonClassTypeToConvertToAnyObject, + type, cs.getASTContext().getAnyObjectType(), locator) { +} + +bool AllowNonClassTypeToConvertToAnyObject::diagnose(bool asNote) const { + auto &cs = getConstraintSystem(); + + auto *locator = getLocator(); + if (locator->getPath().empty()) + return false; + + const auto &last = locator->getPath().back(); + switch (last.getKind()) { + case ConstraintLocator::ContextualType: { + ContextualFailure failure(cs, getFromType(), getToType(), locator); + return failure.diagnose(asNote); + } + + case ConstraintLocator::ApplyArgToParam: { + ArgumentMismatchFailure failure(cs, getFromType(), getToType(), locator); + return failure.diagnose(asNote); + } + + default: + return false; + } +} + +AllowNonClassTypeToConvertToAnyObject * +AllowNonClassTypeToConvertToAnyObject::create(ConstraintSystem &cs, Type type, + ConstraintLocator *locator) { + return new (cs.getAllocator()) + AllowNonClassTypeToConvertToAnyObject(cs, type, locator); +} diff --git a/lib/Sema/CSFix.h b/lib/Sema/CSFix.h index 05959c8ddbaf4..086f9d5e3de89 100644 --- a/lib/Sema/CSFix.h +++ b/lib/Sema/CSFix.h @@ -235,12 +235,15 @@ enum class FixKind : uint8_t { /// Closure return type has to be explicitly specified because it can't be /// inferred in current context e.g. because it's a multi-statement closure. SpecifyClosureReturnType, - - /// Object literal type coudn't be infered because the module where + + /// Object literal type coudn't be inferred because the module where /// the default type that implements the associated literal protocol /// is declared was not imported. SpecifyObjectLiteralTypeImport, + /// Allow any type (and not just class or class-constrained type) to + /// be convertible to AnyObject. + AllowNonClassTypeToConvertToAnyObject, }; class ConstraintFix { @@ -1655,6 +1658,21 @@ class SpecifyObjectLiteralTypeImport final : public ConstraintFix { }; +class AllowNonClassTypeToConvertToAnyObject final : public ContextualMismatch { + AllowNonClassTypeToConvertToAnyObject(ConstraintSystem &cs, Type type, + ConstraintLocator *locator); + +public: + std::string getName() const { + return "allow non-class type to convert to 'AnyObject'"; + } + + bool diagnose(bool asNote = false) const; + + static AllowNonClassTypeToConvertToAnyObject * + create(ConstraintSystem &cs, Type type, ConstraintLocator *locator); +}; + } // end namespace constraints } // end namespace swift diff --git a/lib/Sema/CSSimplify.cpp b/lib/Sema/CSSimplify.cpp index 43a00626b0e8d..46b71306844d5 100644 --- a/lib/Sema/CSSimplify.cpp +++ b/lib/Sema/CSSimplify.cpp @@ -2095,9 +2095,17 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2, // Subtype relation to AnyObject also allows class-bound // existentials that are not @objc and therefore carry // witness tables. - if (!type1->isClassExistentialType() && - !type1->mayHaveSuperclass()) + if (!type1->isClassExistentialType() && !type1->mayHaveSuperclass()) { + if (shouldAttemptFixes()) { + auto *fix = AllowNonClassTypeToConvertToAnyObject::create( + *this, type1, getConstraintLocator(locator)); + + return recordFix(fix) ? getTypeMatchFailure(locator) + : getTypeMatchSuccess(); + } + return getTypeMatchFailure(locator); + } } // Keep going. @@ -8837,6 +8845,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint( case FixKind::AllowMutatingMemberOnRValueBase: case FixKind::AllowTupleSplatForSingleParameter: case FixKind::AllowInvalidUseOfTrailingClosure: + case FixKind::AllowNonClassTypeToConvertToAnyObject: case FixKind::SpecifyClosureReturnType: llvm_unreachable("handled elsewhere"); } From ec3b783380a984c2ee1531108e08170a85ace793 Mon Sep 17 00:00:00 2001 From: Pavel Yaskevich Date: Wed, 29 Jan 2020 00:37:39 -0800 Subject: [PATCH 2/2] [Diagnostics] Improve diagnostic for invalid conversion to AnyObject --- include/swift/AST/DiagnosticsSema.def | 17 +++++- lib/Sema/CSDiagnostics.cpp | 56 +++++++++++++------ lib/Sema/CSDiagnostics.h | 2 +- test/ClangImporter/attr-swift_private.swift | 2 +- test/Constraints/bridging.swift | 10 ++-- test/Generics/existential_restrictions.swift | 2 +- test/Interpreter/SDK/misc_osx.swift | 2 +- test/Parse/metatype_object_conversion.swift | 7 +-- ...g_metatype_cast_to_reference_no_objc.swift | 4 +- 9 files changed, 68 insertions(+), 34 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index fef7af3b04473..02438f31c3bd5 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -338,6 +338,10 @@ ERROR(cannot_convert_initializer_value,none, "cannot convert value of type %0 to specified type %1", (Type,Type)) ERROR(cannot_convert_initializer_value_protocol,none, "value of type %0 does not conform to specified type %1", (Type,Type)) +ERROR(cannot_convert_initializer_value_anyobject,none, + "value of type %0 expected to be instance of class or " + "class-constrained type", + (Type, Type)) ERROR(cannot_convert_initializer_value_nil,none, "'nil' cannot initialize specified type %0", (Type)) @@ -346,6 +350,10 @@ ERROR(cannot_convert_to_return_type,none, (Type,Type)) ERROR(cannot_convert_to_return_type_protocol,none, "return expression of type %0 does not conform to %1", (Type,Type)) +ERROR(cannot_convert_return_type_to_anyobject,none, + "return expression of type %0 expected to be an instance of " + "a class or class-constrained type", + (Type, Type)) ERROR(cannot_convert_to_return_type_nil,none, "'nil' is incompatible with return type %0", (Type)) @@ -440,7 +448,10 @@ NOTE(candidate_performs_illegal_ephemeral_conv,none, ERROR(cannot_convert_argument_value_protocol,none, "argument type %0 does not conform to expected type %1", (Type, Type)) - +ERROR(cannot_convert_argument_value_anyobject,none, + "argument type %0 expected to be an instance of " + "a class or class-constrained type", + (Type, Type)) ERROR(cannot_convert_argument_value_nil,none, "'nil' is not compatible with expected argument type %0", (Type)) @@ -536,6 +547,10 @@ NOTE(assign_protocol_conformance_fix_it,none, ERROR(cannot_convert_assign_protocol,none, "value of type %0 does not conform to %1 in assignment", (Type, Type)) +ERROR(cannot_convert_assign_anyobject,none, + "value of type %0 expected to be an instance of " + "a class or class-constrained type in assignment", + (Type, Type)) ERROR(cannot_convert_assign_nil,none, "'nil' cannot be assigned to type %0", (Type)) diff --git a/lib/Sema/CSDiagnostics.cpp b/lib/Sema/CSDiagnostics.cpp index b8cf4a95a75bb..b85e37691bb76 100644 --- a/lib/Sema/CSDiagnostics.cpp +++ b/lib/Sema/CSDiagnostics.cpp @@ -1977,7 +1977,7 @@ bool ContextualFailure::diagnoseAsError() { CTP = CTP_ClosureResult; } - if (auto msg = getDiagnosticFor(CTP, toType->isExistentialType())) { + if (auto msg = getDiagnosticFor(CTP, toType)) { diagnostic = *msg; break; } @@ -2276,11 +2276,9 @@ bool ContextualFailure::diagnoseCoercionToUnrelatedType() const { if (auto *coerceExpr = dyn_cast(anchor)) { auto fromType = getType(coerceExpr->getSubExpr()); auto toType = getType(coerceExpr->getCastTypeLoc()); - - auto diagnostic = - getDiagnosticFor(CTP_CoerceOperand, - /*forProtocol=*/toType->isExistentialType()); - + + auto diagnostic = getDiagnosticFor(CTP_CoerceOperand, toType); + auto diag = emitDiagnostic(anchor->getLoc(), *diagnostic, fromType, toType); diag.highlight(anchor->getSourceRange()); @@ -2844,15 +2842,24 @@ bool ContextualFailure::isIntegerToStringIndexConversion() const { Optional> ContextualFailure::getDiagnosticFor(ContextualTypePurpose context, - bool forProtocol) { + Type contextualType) { + auto forProtocol = contextualType->isExistentialType(); switch (context) { - case CTP_Initialization: + case CTP_Initialization: { + if (contextualType->isAnyObject()) + return diag::cannot_convert_initializer_value_anyobject; + return forProtocol ? diag::cannot_convert_initializer_value_protocol : diag::cannot_convert_initializer_value; + } case CTP_ReturnStmt: - case CTP_ReturnSingleExpr: + case CTP_ReturnSingleExpr: { + if (contextualType->isAnyObject()) + return diag::cannot_convert_return_type_to_anyobject; + return forProtocol ? diag::cannot_convert_to_return_type_protocol : diag::cannot_convert_to_return_type; + } case CTP_EnumCaseRawValue: return diag::cannot_convert_raw_initializer_value; case CTP_DefaultParameter: @@ -2861,9 +2868,13 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context, case CTP_YieldByValue: return forProtocol ? diag::cannot_convert_yield_value_protocol : diag::cannot_convert_yield_value; - case CTP_CallArgument: + case CTP_CallArgument: { + if (contextualType->isAnyObject()) + return diag::cannot_convert_argument_value_anyobject; + return forProtocol ? diag::cannot_convert_argument_value_protocol : diag::cannot_convert_argument_value; + } case CTP_ClosureResult: return forProtocol ? diag::cannot_convert_closure_result_protocol : diag::cannot_convert_closure_result; @@ -2879,9 +2890,13 @@ ContextualFailure::getDiagnosticFor(ContextualTypePurpose context, case CTP_CoerceOperand: return forProtocol ? diag::cannot_convert_coerce_protocol : diag::cannot_convert_coerce; - case CTP_AssignSource: + case CTP_AssignSource: { + if (contextualType->isAnyObject()) + return diag::cannot_convert_assign_anyobject; + return forProtocol ? diag::cannot_convert_assign_protocol : diag::cannot_convert_assign; + } case CTP_SubscriptAssignSource: return forProtocol ? diag::cannot_convert_subscript_assign_protocol : diag::cannot_convert_subscript_assign; @@ -2908,7 +2923,7 @@ bool TupleContextualFailure::diagnoseAsError() { else if ((purpose == CTP_Initialization) && !cs.getContextualType(getAnchor())) diagnostic = diag::tuple_types_not_convertible; - else if (auto diag = getDiagnosticFor(purpose, /*forProtocol=*/false)) + else if (auto diag = getDiagnosticFor(purpose, getToType())) diagnostic = *diag; else return false; @@ -2919,7 +2934,7 @@ bool TupleContextualFailure::diagnoseAsError() { bool FunctionTypeMismatch::diagnoseAsError() { auto purpose = getContextualTypePurpose(); - auto diagnostic = getDiagnosticFor(purpose, /*forProtocol=*/false); + auto diagnostic = getDiagnosticFor(purpose, getToType()); if (!diagnostic) return false; @@ -4839,17 +4854,16 @@ bool MissingContextualConformanceFailure::diagnoseAsError() { if (path.empty()) { assert(isa(anchor)); if (isa(cast(anchor)->getDest())) { - diagnostic = - getDiagnosticFor(CTP_SubscriptAssignSource, /*forProtocol=*/true); + diagnostic = getDiagnosticFor(CTP_SubscriptAssignSource, getToType()); } else { - diagnostic = getDiagnosticFor(CTP_AssignSource, /*forProtocol=*/true); + diagnostic = getDiagnosticFor(CTP_AssignSource, getToType()); } } else { const auto &last = path.back(); switch (last.getKind()) { case ConstraintLocator::ContextualType: assert(Context != CTP_Unused); - diagnostic = getDiagnosticFor(Context, /*forProtocol=*/true); + diagnostic = getDiagnosticFor(Context, getToType()); break; case ConstraintLocator::SequenceElementType: { @@ -5277,7 +5291,7 @@ bool InOutConversionFailure::diagnoseAsError() { assert(locator->findLast()); auto contextualType = cs.getContextualType(anchor); auto purpose = getContextualTypePurpose(); - auto diagnostic = getDiagnosticFor(purpose, /*forProtocol=*/false); + auto diagnostic = getDiagnosticFor(purpose, contextualType); if (!diagnostic) return false; @@ -5385,6 +5399,12 @@ bool ArgumentMismatchFailure::diagnoseAsError() { auto argType = getFromType(); auto paramType = getToType(); + if (paramType->isAnyObject()) { + emitDiagnostic(getLoc(), diag::cannot_convert_argument_value_anyobject, + argType, paramType); + return true; + } + Diag diagnostic = diag::cannot_convert_argument_value; // If parameter type is a protocol value, let's says that diff --git a/lib/Sema/CSDiagnostics.h b/lib/Sema/CSDiagnostics.h index 90f64c5175acf..386ea94c8831a 100644 --- a/lib/Sema/CSDiagnostics.h +++ b/lib/Sema/CSDiagnostics.h @@ -653,7 +653,7 @@ class ContextualFailure : public FailureDiagnostic { ContextualTypePurpose getContextualTypePurpose() const { return CTP; } static Optional> - getDiagnosticFor(ContextualTypePurpose context, bool forProtocol); + getDiagnosticFor(ContextualTypePurpose context, Type contextualType); }; /// Diagnose errors related to converting function type which diff --git a/test/ClangImporter/attr-swift_private.swift b/test/ClangImporter/attr-swift_private.swift index 05ddc81835f30..f249454b15d96 100644 --- a/test/ClangImporter/attr-swift_private.swift +++ b/test/ClangImporter/attr-swift_private.swift @@ -121,7 +121,7 @@ func testCF(_ a: __PrivCFType, b: __PrivCFSub, c: __PrivInt) { makeSureAnyObject(a) makeSureAnyObject(b) #if !IRGEN - makeSureAnyObject(c) // expected-error {{argument type '__PrivInt' (aka 'Int32') does not conform to expected type 'AnyObject'}} + makeSureAnyObject(c) // expected-error {{argument type '__PrivInt' (aka 'Int32') expected to be an instance of a class or class-constrained type}} #endif } diff --git a/test/Constraints/bridging.swift b/test/Constraints/bridging.swift index 4cdc52e3e2626..c40fb0bdf181d 100644 --- a/test/Constraints/bridging.swift +++ b/test/Constraints/bridging.swift @@ -89,7 +89,7 @@ func bridgeToObjC(_ s: BridgedStruct) -> BridgedClass { } func bridgeToAnyObject(_ s: BridgedStruct) -> AnyObject { - return s // expected-error{{return expression of type 'BridgedStruct' does not conform to 'AnyObject'}} + return s // expected-error{{return expression of type 'BridgedStruct' expected to be an instance of a class or class-constrained type}} return s as AnyObject } @@ -344,14 +344,14 @@ func forceUniversalBridgeToAnyObject(a: T, b: U, c: An z = g as AnyObject z = h as AnyObject - z = a // expected-error{{does not conform to 'AnyObject'}} + z = a // expected-error{{value of type 'T' expected to be an instance of a class or class-constrained type in assignment}} z = b - z = c // expected-error{{does not conform to 'AnyObject'}} expected-note {{cast 'Any' to 'AnyObject'}} {{8-8= as AnyObject}} - z = d // expected-error{{does not conform to 'AnyObject'}} + z = c // expected-error{{value of type 'Any' expected to be an instance of a class or class-constrained type in assignment}} expected-note {{cast 'Any' to 'AnyObject'}} {{8-8= as AnyObject}} + z = d // expected-error{{value of type 'KnownUnbridged' expected to be an instance of a class or class-constrained type in assignment}} z = e z = f z = g - z = h // expected-error{{does not conform to 'AnyObject'}} + z = h // expected-error{{value of type 'String' expected to be an instance of a class or class-constrained type in assignment}} _ = z } diff --git a/test/Generics/existential_restrictions.swift b/test/Generics/existential_restrictions.swift index ae652220daa2b..fbf2f29375fc5 100644 --- a/test/Generics/existential_restrictions.swift +++ b/test/Generics/existential_restrictions.swift @@ -25,7 +25,7 @@ func fT(_ t: T) { } func testPassExistential(_ p: P, op: OP, opp: OP & P, cp: CP, sp: SP, any: Any, ao: AnyObject) { fP(p) // expected-error{{value of protocol type 'P' cannot conform to 'P'; only struct/enum/class types can conform to protocols}} fAO(p) // expected-error{{global function 'fAO' requires that 'P' be a class type}} - fAOE(p) // expected-error{{argument type 'P' does not conform to expected type 'AnyObject'}} + fAOE(p) // expected-error{{argument type 'P' expected to be an instance of a class or class-constrained type}} fT(p) fOP(op) diff --git a/test/Interpreter/SDK/misc_osx.swift b/test/Interpreter/SDK/misc_osx.swift index fabd2413023c3..15383cf784077 100644 --- a/test/Interpreter/SDK/misc_osx.swift +++ b/test/Interpreter/SDK/misc_osx.swift @@ -13,5 +13,5 @@ func testFSEventStreamRef(stream: FSEventStreamRef) { FSEventStreamRetain(stream) // no-warning FSEventStreamRelease(stream) - let _: AnyObject = stream // expected-error {{value of type 'FSEventStreamRef' (aka 'OpaquePointer') does not conform to specified type 'AnyObject'}} + let _: AnyObject = stream // expected-error {{value of type 'FSEventStreamRef' (aka 'OpaquePointer') expected to be instance of class or class-constrained type}} } diff --git a/test/Parse/metatype_object_conversion.swift b/test/Parse/metatype_object_conversion.swift index 96f27402dc86f..2dc87ff1e87bb 100644 --- a/test/Parse/metatype_object_conversion.swift +++ b/test/Parse/metatype_object_conversion.swift @@ -12,15 +12,14 @@ func takesAnyObject(_ x: AnyObject) {} func concreteTypes() { takesAnyObject(C.self) - // TODO: Better error messages - takesAnyObject(S.self) // expected-error{{argument type 'S.Type' does not conform to expected type 'AnyObject'}} - takesAnyObject(ClassConstrainedProto.self) // expected-error{{argument type 'ClassConstrainedProto.Protocol' does not conform to expected type 'AnyObject'}} + takesAnyObject(S.self) // expected-error{{argument type 'S.Type' expected to be an instance of a class or class-constrained type}} + takesAnyObject(ClassConstrainedProto.self) // expected-error{{argument type 'ClassConstrainedProto.Protocol' expected to be an instance of a class or class-constrained type}} } func existentialMetatypes(nonClass: NonClassProto.Type, classConstrained: ClassConstrainedProto.Type, compo: (NonClassProto & ClassConstrainedProto).Type) { - takesAnyObject(nonClass) // expected-error{{argument type 'NonClassProto.Type' does not conform to expected type 'AnyObject'}} + takesAnyObject(nonClass) // expected-error{{argument type 'NonClassProto.Type' expected to be an instance of a class or class-constrained type}} takesAnyObject(classConstrained) takesAnyObject(compo) } diff --git a/test/Sema/diag_metatype_cast_to_reference_no_objc.swift b/test/Sema/diag_metatype_cast_to_reference_no_objc.swift index 28566417e0fbf..abca5b92aa279 100644 --- a/test/Sema/diag_metatype_cast_to_reference_no_objc.swift +++ b/test/Sema/diag_metatype_cast_to_reference_no_objc.swift @@ -3,6 +3,6 @@ class C {} func test(c: AnyClass) { - let _: AnyObject = c // expected-error {{value of type 'AnyClass' (aka 'AnyObject.Type') does not conform to specified type 'AnyObject'}} - let _: AnyObject = C.self // expected-error {{value of type 'C.Type' does not conform to specified type 'AnyObject'}} + let _: AnyObject = c // expected-error {{value of type 'AnyClass' (aka 'AnyObject.Type') expected to be instance of class or class-constrained type}} + let _: AnyObject = C.self // expected-error {{value of type 'C.Type' expected to be instance of class or class-constrained type}} }