Skip to content

[Diagnostics] Port invalid conversion to AnyObject diagnostic #29528

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

Merged
merged 2 commits into from
Jan 29, 2020
Merged
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
17 changes: 16 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand All @@ -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))

Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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))

Expand Down
56 changes: 38 additions & 18 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -2276,11 +2276,9 @@ bool ContextualFailure::diagnoseCoercionToUnrelatedType() const {
if (auto *coerceExpr = dyn_cast<CoerceExpr>(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());
Expand Down Expand Up @@ -2844,15 +2842,24 @@ bool ContextualFailure::isIntegerToStringIndexConversion() const {

Optional<Diag<Type, Type>>
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:
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -4839,17 +4854,16 @@ bool MissingContextualConformanceFailure::diagnoseAsError() {
if (path.empty()) {
assert(isa<AssignExpr>(anchor));
if (isa<SubscriptExpr>(cast<AssignExpr>(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: {
Expand Down Expand Up @@ -5277,7 +5291,7 @@ bool InOutConversionFailure::diagnoseAsError() {
assert(locator->findLast<LocatorPathElt::ContextualType>());
auto contextualType = cs.getContextualType(anchor);
auto purpose = getContextualTypePurpose();
auto diagnostic = getDiagnosticFor(purpose, /*forProtocol=*/false);
auto diagnostic = getDiagnosticFor(purpose, contextualType);

if (!diagnostic)
return false;
Expand Down Expand Up @@ -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<Type, Type> diagnostic = diag::cannot_convert_argument_value;

// If parameter type is a protocol value, let's says that
Expand Down
2 changes: 1 addition & 1 deletion lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ class ContextualFailure : public FailureDiagnostic {
ContextualTypePurpose getContextualTypePurpose() const { return CTP; }

static Optional<Diag<Type, Type>>
getDiagnosticFor(ContextualTypePurpose context, bool forProtocol);
getDiagnosticFor(ContextualTypePurpose context, Type contextualType);
};

/// Diagnose errors related to converting function type which
Expand Down
37 changes: 37 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
22 changes: 20 additions & 2 deletions lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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

Expand Down
13 changes: 11 additions & 2 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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");
}
Expand Down
2 changes: 1 addition & 1 deletion test/ClangImporter/attr-swift_private.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
10 changes: 5 additions & 5 deletions test/Constraints/bridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -344,14 +344,14 @@ func forceUniversalBridgeToAnyObject<T, U: KnownClassProtocol>(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
}
Expand Down
2 changes: 1 addition & 1 deletion test/Generics/existential_restrictions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func fT<T>(_ 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)
Expand Down
2 changes: 1 addition & 1 deletion test/Interpreter/SDK/misc_osx.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}
7 changes: 3 additions & 4 deletions test/Parse/metatype_object_conversion.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
4 changes: 2 additions & 2 deletions test/Sema/diag_metatype_cast_to_reference_no_objc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
}