[CS] Improve diagnostics for non-metatype type(of:) contextual type

Emit a custom diagnostic for this case, and handle holes.
This commit is contained in:
Hamish Knight
2025-10-04 21:48:09 +01:00
parent 9a9a6a3b98
commit d65f28984a
7 changed files with 116 additions and 2 deletions

View File

@@ -359,6 +359,11 @@ ERROR(cannot_convert_return_type_to_anyobject,none,
ERROR(cannot_convert_to_return_type_nil,none,
"'nil' is incompatible with return type %0", (Type))
ERROR(cannot_convert_metatype_to_non_metatype,none,
"cannot convert metatype %0 to non-metatype %1", (Type, Type))
ERROR(cannot_convert_typeof_to_non_metatype,none,
"cannot convert 'type(of:)' metatype to non-metatype %0", (Type))
ERROR(cannot_convert_thrown_type,none,
"thrown expression type %0 %select{cannot be converted to error type %1|"
"does not conform to 'Error'}2",

View File

@@ -394,6 +394,9 @@ enum class FixKind : uint8_t {
/// Ignore a type imposed by an assignment destination e.g. `let x: Int = ...`
IgnoreAssignmentDestinationType,
/// Ignore a non-metatype contextual type for a `type(of:)` expression.
IgnoreNonMetatypeDynamicType,
/// Allow argument-to-parameter subtyping even when parameter type
/// is marked as `inout`.
AllowConversionThroughInOut,
@@ -2434,6 +2437,30 @@ public:
}
};
/// Ignore a non-metatype contextual type for a `type(of:)` expression, for
/// example `let x: Int = type(of: foo)`.
class IgnoreNonMetatypeDynamicType final : public ContextualMismatch {
IgnoreNonMetatypeDynamicType(ConstraintSystem &cs, Type instanceTy,
Type metatypeTy, ConstraintLocator *locator)
: ContextualMismatch(cs, FixKind::IgnoreNonMetatypeDynamicType,
instanceTy, metatypeTy, locator) {}
public:
std::string getName() const override {
return "ignore non-metatype result for 'type(of:)'";
}
bool diagnose(const Solution &solution, bool asNote = false) const override;
static IgnoreNonMetatypeDynamicType *create(ConstraintSystem &cs,
Type instanceTy, Type metatypeTy,
ConstraintLocator *locator);
static bool classof(const ConstraintFix *fix) {
return fix->getKind() == FixKind::IgnoreNonMetatypeDynamicType;
}
};
/// If this is an argument-to-parameter conversion which is associated with
/// `inout` parameter, subtyping is not permitted, types have to
/// be identical.

View File

@@ -8189,6 +8189,20 @@ bool AssignmentTypeMismatchFailure::diagnoseAsNote() {
return false;
}
bool NonMetatypeDynamicTypeFailure::diagnoseAsError() {
auto instanceTy = getFromType();
auto metatypeTy = getToType();
if (instanceTy->isBareErrorType()) {
emitDiagnostic(diag::cannot_convert_typeof_to_non_metatype, metatypeTy)
.highlight(getSourceRange());
} else {
emitDiagnostic(diag::cannot_convert_metatype_to_non_metatype,
MetatypeType::get(instanceTy), metatypeTy)
.highlight(getSourceRange());
}
return true;
}
bool MissingContextualBaseInMemberRefFailure::diagnoseAsError() {
auto *anchor = castToExpr(getAnchor());
// Member reference could be wrapped into a number of parens

View File

@@ -2428,6 +2428,15 @@ private:
bool diagnoseMissingConformance() const;
};
class NonMetatypeDynamicTypeFailure final : public ContextualFailure {
public:
NonMetatypeDynamicTypeFailure(const Solution &solution, Type instanceTy,
Type metatypeTy, ConstraintLocator *locator)
: ContextualFailure(solution, instanceTy, metatypeTy, locator) {}
bool diagnoseAsError() override;
};
class MissingContextualBaseInMemberRefFailure final : public FailureDiagnostic {
DeclNameRef MemberName;

View File

@@ -1825,6 +1825,21 @@ IgnoreAssignmentDestinationType::create(ConstraintSystem &cs, Type sourceTy,
IgnoreAssignmentDestinationType(cs, sourceTy, destTy, locator);
}
IgnoreNonMetatypeDynamicType *
IgnoreNonMetatypeDynamicType::create(ConstraintSystem &cs, Type instanceTy,
Type metatypeTy,
ConstraintLocator *locator) {
return new (cs.getAllocator())
IgnoreNonMetatypeDynamicType(cs, instanceTy, metatypeTy, locator);
}
bool IgnoreNonMetatypeDynamicType::diagnose(const Solution &solution,
bool asNote) const {
NonMetatypeDynamicTypeFailure failure(solution, getFromType(), getToType(),
getLocator());
return failure.diagnose(asNote);
}
bool AllowInOutConversion::diagnose(const Solution &solution,
bool asNote) const {
InOutConversionFailure failure(solution, getFromType(), getToType(),

View File

@@ -12541,8 +12541,24 @@ ConstraintSystem::simplifyDynamicTypeOfConstraint(
locator);
}
// It's definitely not either kind of metatype, so we can
// report failure right away.
// We don't have a non-metatype result, produce a fix.
if (shouldAttemptFixes()) {
// If we have a hole as a contextual type, eagerly produce holes in the
// argument of `type(of:)`.
if (type1->isPlaceholder()) {
recordTypeVariablesAsHoles(type2);
return SolutionKind::Solved;
}
// Otherwise we have some invalid contextual type, record a fix and let the
// argument be turned into a hole if needed.
recordAnyTypeVarAsPotentialHole(type2);
recordFix(IgnoreNonMetatypeDynamicType::create(
*this, type2, type1, getConstraintLocator(locator)));
return SolutionKind::Solved;
}
return SolutionKind::Error;
}
@@ -16100,6 +16116,7 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::IgnoreMissingEachKeyword:
case FixKind::AllowInlineArrayLiteralCountMismatch:
case FixKind::TooManyDynamicMemberLookups:
case FixKind::IgnoreNonMetatypeDynamicType:
case FixKind::IgnoreIsolatedConformance:
llvm_unreachable("handled elsewhere");
}

View File

@@ -87,3 +87,30 @@ do {
}
}
}
_ = { x in // expected-error {{cannot infer type of closure parameter 'x' without a type annotation}}
let _: Undefined = Swift.type(of: x)
// expected-error@-1 {{cannot find type 'Undefined' in scope}}
}
_ = {
func foo<T>() -> T {}
let _: Undefined = Swift.type(of: foo())
// expected-error@-1 {{cannot find type 'Undefined' in scope}}
}
_ = {
let _: Undefined = Swift.type(of: .foo)
// expected-error@-1 {{cannot find type 'Undefined' in scope}}
}
let _: Int = Swift.type(of: .foo)
// expected-error@-1 {{cannot convert 'type(of:)' metatype to non-metatype 'Int'}}
let _ = Swift.type(of: .foo)
// expected-error@-1 {{cannot infer contextual base in reference to member 'foo'}}
// FIXME: Ideally we'd include the type of the argument in the diagnostic, currently
// we bind it to a hole before we open the closure.
let _: Int = Swift.type(of: { (); return 0 }())
// expected-error@-1 {{cannot convert 'type(of:)' metatype to non-metatype 'Int'}}
let _: Undefined = Swift.type(of: { (); return 0 }())
// expected-error@-1 {{cannot find type 'Undefined' in scope}}