Skip to content

[6.2][Diagnostics] A collection of diagnostic fixes/improvements #81945

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 12 commits into from
Jun 4, 2025
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
5 changes: 5 additions & 0 deletions include/swift/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,11 @@ class ConstraintFix {
return false;
}

template <typename E>
bool directlyAt() const {
return getLocator()->directlyAt<E>();
}

void print(llvm::raw_ostream &Out) const;

SWIFT_DEBUG_DUMP;
Expand Down
9 changes: 9 additions & 0 deletions include/swift/Sema/ConstraintSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -3484,6 +3484,15 @@ class ConstraintSystem {
return nullptr;
}

Expr *getSemanticsProvidingParentExpr(Expr *expr) {
while (auto *parent = getParentExpr(expr)) {
if (parent->getSemanticsProvidingExpr() == parent)
return parent;
expr = parent;
}
return nullptr;
}

/// Retrieve the depth of the given expression.
std::optional<unsigned> getExprDepth(Expr *expr) {
if (auto result = getExprDepthAndParent(expr))
Expand Down
7 changes: 6 additions & 1 deletion lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1114,7 +1114,12 @@ bool GenericArgumentsMismatchFailure::diagnoseAsError() {
if (!diagnostic)
return false;

emitDiagnosticAt(::getLoc(anchor), *diagnostic, fromType, toType);
{
auto diag =
emitDiagnosticAt(::getLoc(anchor), *diagnostic, fromType, toType);
(void)tryFixIts(diag);
}

emitNotesForMismatches();
return true;
}
Expand Down
188 changes: 123 additions & 65 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4039,6 +4039,12 @@ ConstraintSystem::matchDeepEqualityTypes(Type type1, Type type2,
getConstraintLocator(locator, {LocatorPathElt::GenericType(type1),
LocatorPathElt::GenericType(type2)});

// Optionals have a lot of special diagnostics and only one
// generic argument so if we're dealing with one, let's allow
// `repairFailures` to take care of it.
if (bound1->getDecl()->isOptionalDecl())
return matchDeepTypeArguments(*this, subflags, args1, args2, baseLoc);

auto argMatchingFlags = subflags;
// Allow the solver to produce separate fixes while matching
// key path's root/value to a contextual type instead of the
Expand All @@ -4049,13 +4055,6 @@ ConstraintSystem::matchDeepEqualityTypes(Type type1, Type type2,
argMatchingFlags |= TMF_MatchingGenericArguments;
}

// Optionals have a lot of special diagnostics and only one
// generic argument so if we' re dealing with one, don't produce generic
// arguments mismatch fixes.
if (bound1->getDecl()->isOptionalDecl())
return matchDeepTypeArguments(*this, argMatchingFlags, args1, args2,
baseLoc);

SmallVector<unsigned, 4> mismatches;
auto result = matchDeepTypeArguments(
*this, argMatchingFlags, args1, args2, baseLoc,
Expand Down Expand Up @@ -4282,7 +4281,8 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2,
if (!path.empty()) {
auto last = path.back();

if (last.is<LocatorPathElt::ApplyArgToParam>()) {
if (last.is<LocatorPathElt::ApplyArgToParam>() ||
last.is<LocatorPathElt::AutoclosureResult>()) {
auto proto = protoDecl->getDeclaredInterfaceType();
// Impact is 2 here because there are two failures
// 1 - missing conformance and 2 - incorrect argument type.
Expand Down Expand Up @@ -4310,6 +4310,15 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2,
break;
}

if ((isExpr<ArrayExpr>(anchor) || isExpr<DictionaryExpr>(anchor)) &&
last.is<LocatorPathElt::TupleElement>()) {
auto *fix = CollectionElementContextualMismatch::create(
*this, type1, type2, getConstraintLocator(anchor, path));
if (recordFix(fix, /*impact=*/2))
return getTypeMatchFailure(locator);
break;
}

// TODO(diagnostics): If there are any requirement failures associated
// with result types which are part of a function type conversion,
// let's record general conversion mismatch in order for it to capture
Expand Down Expand Up @@ -4979,8 +4988,18 @@ repairViaOptionalUnwrap(ConstraintSystem &cs, Type fromType, Type toType,

// First, let's check whether it has been determined that
// it was incorrect to use `?` in this position.
if (cs.hasFixFor(cs.getConstraintLocator(subExpr), FixKind::RemoveUnwrap))
if (cs.hasFixFor(cs.getConstraintLocator(subExpr), FixKind::RemoveUnwrap)) {
if (auto *typeVar =
fromType->getOptionalObjectType()->getAs<TypeVariableType>()) {
// If the optional chain is invalid let's unwrap optional and
// re-introduce the constraint to be solved later once both sides
// are sufficiently resolved, this would allow to diagnose not only
// the invalid unwrap but an invalid conversion (if any) as well.
cs.addConstraint(matchKind, typeVar, toType,
cs.getConstraintLocator(locator));
}
return true;
}

auto type = cs.getType(subExpr);
// If the type of sub-expression is optional, type of the
Expand Down Expand Up @@ -5775,6 +5794,41 @@ bool ConstraintSystem::repairFailures(
break;
}

// There is no subtyping between object types of inout argument/parameter.
if (auto argConv = path.back().getAs<LocatorPathElt::ApplyArgToParam>()) {
// Attempt conversions first.
if (hasAnyRestriction())
break;

// Unwraps are allowed to preserve l-valueness so we can suggest
// them here.
if (repairViaOptionalUnwrap(*this, lhs, rhs, matchKind,
conversionsOrFixes, locator))
return true;

auto *loc = getConstraintLocator(locator);

auto result = matchTypes(lhs, rhs, ConstraintKind::Conversion,
TMF_ApplyingFix, locator);

ConstraintFix *fix = nullptr;
if (result.isFailure()) {
// If this is a "destination" argument to a mutating operator
// like `+=`, let's consider it contextual and only attempt
// to fix type mismatch on the "source" right-hand side of
// such operators.
if (isOperatorArgument(loc) && argConv->getArgIdx() == 0)
break;

fix = AllowArgumentMismatch::create(*this, lhs, rhs, loc);
} else {
fix = AllowInOutConversion::create(*this, lhs, rhs, loc);
}

conversionsOrFixes.push_back(fix);
break;
}

// If this is a problem with result type of a subscript setter,
// let's re-attempt to repair without l-value conversion in the
// locator to fix underlying type mismatch.
Expand All @@ -5794,7 +5848,7 @@ bool ConstraintSystem::repairFailures(
break;
}

LLVM_FALLTHROUGH;
break;
}

case ConstraintLocator::ApplyArgToParam: {
Expand Down Expand Up @@ -5874,52 +5928,6 @@ bool ConstraintSystem::repairFailures(
if (repairByTreatingRValueAsLValue(lhs, rhs))
break;

// If the problem is related to missing unwrap, there is a special
// fix for that.
if (lhs->getOptionalObjectType() && !rhs->getOptionalObjectType()) {
// If this is an attempt to check whether optional conforms to a
// particular protocol, let's do that before attempting to force
// unwrap the optional.
if (hasConversionOrRestriction(ConversionRestrictionKind::Existential))
break;

auto result = matchTypes(lhs->getOptionalObjectType(), rhs, matchKind,
TMF_ApplyingFix, locator);

if (result.isSuccess()) {
conversionsOrFixes.push_back(
ForceOptional::create(*this, lhs, rhs, loc));
break;
}
}

// There is no subtyping between object types of inout argument/parameter.
if (elt.getKind() == ConstraintLocator::LValueConversion) {
auto result = matchTypes(lhs, rhs, ConstraintKind::Conversion,
TMF_ApplyingFix, locator);

ConstraintFix *fix = nullptr;
if (result.isFailure()) {
// If this is a "destination" argument to a mutating operator
// like `+=`, let's consider it contextual and only attempt
// to fix type mismatch on the "source" right-hand side of
// such operators.
if (isOperatorArgument(loc) &&
loc->findLast<LocatorPathElt::ApplyArgToParam>()->getArgIdx() == 0)
break;

fix = AllowArgumentMismatch::create(*this, lhs, rhs, loc);
} else {
fix = AllowInOutConversion::create(*this, lhs, rhs, loc);
}

conversionsOrFixes.push_back(fix);
break;
}

if (elt.getKind() != ConstraintLocator::ApplyArgToParam)
break;

// If argument in l-value type and parameter is `inout` or a pointer,
// let's see if it's generic parameter matches and suggest adding explicit
// `&`.
Expand Down Expand Up @@ -6046,7 +6054,7 @@ bool ConstraintSystem::repairFailures(

if (repairViaOptionalUnwrap(*this, lhs, rhs, matchKind, conversionsOrFixes,
locator))
break;
return true;

{
auto *calleeLocator = getCalleeLocator(loc);
Expand Down Expand Up @@ -6856,9 +6864,28 @@ bool ConstraintSystem::repairFailures(
if (!path.empty() && path.back().is<LocatorPathElt::PackElement>())
path.pop_back();

if (!path.empty() && path.back().is<LocatorPathElt::AnyRequirement>()) {
return repairFailures(lhs, rhs, matchKind, flags, conversionsOrFixes,
getConstraintLocator(anchor, path));
if (!path.empty()) {
if (path.back().is<LocatorPathElt::AnyRequirement>()) {
return repairFailures(lhs, rhs, matchKind, flags, conversionsOrFixes,
getConstraintLocator(anchor, path));
}

if (auto argConv = path.back().getAs<LocatorPathElt::ApplyArgToParam>()) {
auto argIdx = argConv->getArgIdx();
auto paramIdx = argConv->getParamIdx();

auto *argLoc = getConstraintLocator(anchor, path);
if (auto overload = findSelectedOverloadFor(getCalleeLocator(argLoc))) {
auto *overloadTy =
simplifyType(overload->boundType)->castTo<FunctionType>();
auto *argList = getArgumentList(argLoc);
ASSERT(argList);
conversionsOrFixes.push_back(AllowArgumentMismatch::create(
*this, getType(argList->getExpr(argIdx)),
overloadTy->getParams()[paramIdx].getPlainType(), argLoc));
return true;
}
}
}

// When the solver sets `TMF_MatchingGenericArguments` it means
Expand Down Expand Up @@ -6915,12 +6942,19 @@ bool ConstraintSystem::repairFailures(
path.pop_back();

ConstraintFix *fix = nullptr;
if (!path.empty() && path.back().is<LocatorPathElt::AnyRequirement>()) {
auto *fixLoc = getConstraintLocator(anchor, path);

if (fixLoc->isLastElement<LocatorPathElt::AnyRequirement>()) {
fix = fixRequirementFailure(*this, fromType, toType, anchor, path);
} else if (fixLoc->isLastElement<LocatorPathElt::TupleElement>()) {
return repairFailures(lhs, rhs, matchKind, flags, conversionsOrFixes,
fixLoc);
} else if (!lhs->mayHaveSuperclass() && rhs->isAnyObject()) {
fix = AllowNonClassTypeToConvertToAnyObject::create(*this, fromType,
fixLoc);
} else {
fix = GenericArgumentsMismatch::create(
*this, fromType, toType, {genericArgElt.getIndex()},
getConstraintLocator(anchor, path));
*this, fromType, toType, {genericArgElt.getIndex()}, fixLoc);
}

if (!fix)
Expand Down Expand Up @@ -11410,6 +11444,14 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyMemberConstraint(
// `key path` constraint can't be retired until all components
// are simplified.
addTypeVariableConstraintsToWorkList(memberTypeVar);
} else if (locator->getAnchor().is<Expr *>() &&
!getSemanticsProvidingParentExpr(
getAsExpr(locator->getAnchor()))) {
// If there are no contextual expressions that could provide
// a type for the member type variable, let's default it to
// a placeholder eagerly so it could be propagated to the
// pattern if necessary.
recordTypeVariablesAsHoles(memberTypeVar);
} else if (locator->isLastElement<LocatorPathElt::PatternMatch>()) {
// Let's handle member patterns specifically because they use
// equality instead of argument application constraint, so allowing
Expand Down Expand Up @@ -15649,14 +15691,31 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::AllowFunctionSpecialization:
case FixKind::IgnoreGenericSpecializationArityMismatch:
case FixKind::IgnoreKeyPathSubscriptIndexMismatch:
case FixKind::AllowMemberRefOnExistential: {
case FixKind::AllowMemberRefOnExistential:
case FixKind::AllowNonClassTypeToConvertToAnyObject: {
return recordFix(fix) ? SolutionKind::Error : SolutionKind::Solved;
}

case FixKind::GenericArgumentsMismatch: {
unsigned impact = 1;
if (type1->isMarkerExistential() || type2->isMarkerExistential())
++impact;

// If generic arguments mismatch ends up being recorded on the result
// of the chain or a try expression it means that there is a contextual
// conversion mismatch.
//
// For optional conversions the solver currently generates a disjunction
// with two choices - bind and optional-to-optional conversion which is
// anchored on the contextual expression. If we can get a fix recorded
// there that would result in a better diagnostic. It's only possible
// for optional-to-optional choice because it doesn't bind the
// variable immediately, so we need to downgrade direct fixes to prevent
// `bind` choice from considered better.
if (fix->directlyAt<OptionalEvaluationExpr>() ||
fix->directlyAt<AnyTryExpr>())
impact += 2;

return recordFix(fix, impact) ? SolutionKind::Error : SolutionKind::Solved;
}

Expand Down Expand Up @@ -15884,7 +15943,6 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyFixConstraint(
case FixKind::DefaultGenericArgument:
case FixKind::AllowMutatingMemberOnRValueBase:
case FixKind::AllowTupleSplatForSingleParameter:
case FixKind::AllowNonClassTypeToConvertToAnyObject:
case FixKind::SpecifyClosureParameterType:
case FixKind::SpecifyClosureReturnType:
case FixKind::AddQualifierToAccessTopLevelName:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,13 @@ func testProtocolNamingConflict() {
let a: ConflictingName1?
var b: ConflictingName1Protocol?
b = a // expected-error {{cannot assign value of type 'ConflictingName1?' to type '(any ConflictingName1Protocol)?'}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('ConflictingName1' and 'any ConflictingName1Protocol') are expected to be equal}}
_ = b

let c: ConflictingName2?
var d: ConflictingName2Protocol?
d = c // expected-error {{cannot assign value of type 'ConflictingName2?' to type '(any ConflictingName2Protocol)?'}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('ConflictingName2' and 'any ConflictingName2Protocol') are expected to be equal}}
_ = d
}

Expand Down
6 changes: 6 additions & 0 deletions test/ClangImporter/cf.swift
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func testOutParametersGood() {
func testOutParametersBad() {
let fridge: CCRefrigerator?
CCRefrigeratorCreateIndirect(fridge) // expected-error {{cannot convert value of type 'CCRefrigerator?' to expected argument type 'UnsafeMutablePointer<CCRefrigerator?>?'}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('CCRefrigerator' and 'UnsafeMutablePointer<CCRefrigerator?>') are expected to be equal}}

let power: CCPowerSupply?
CCRefrigeratorGetPowerSupplyIndirect(0, power)
Expand All @@ -128,21 +129,26 @@ func testOutParametersBad() {
CCRefrigeratorGetItemUnaudited(0, 0, item)
// expected-error@-1:34 {{cannot convert value of type 'Int' to expected argument type 'CCRefrigerator'}}
// expected-error@-2:40 {{cannot convert value of type 'CCItem?' to expected argument type 'UnsafeMutablePointer<Unmanaged<CCItem>?>?'}}
// expected-note@-3 {{arguments to generic parameter 'Wrapped' ('CCItem' and 'UnsafeMutablePointer<Unmanaged<CCItem>?>') are expected to be equal}}
}

func nameCollisions() {
var objc: MyProblematicObject?
var cf: MyProblematicObjectRef?
cf = objc // expected-error {{cannot assign value of type 'MyProblematicObject?' to type 'MyProblematicObjectRef?'}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('MyProblematicObject' and 'MyProblematicObjectRef') are expected to be equal}}
objc = cf // expected-error {{cannot assign value of type 'MyProblematicObjectRef?' to type 'MyProblematicObject?'}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('MyProblematicObjectRef' and 'MyProblematicObject') are expected to be equal}}

var cfAlias: MyProblematicAliasRef?
cfAlias = cf // okay
cf = cfAlias // okay

var otherAlias: MyProblematicAlias?
otherAlias = cfAlias // expected-error {{cannot assign value of type 'MyProblematicAliasRef?' (aka 'Optional<MyProblematicObjectRef>') to type 'MyProblematicAlias?' (aka 'Optional<Float>')}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('MyProblematicAliasRef' (aka 'MyProblematicObjectRef') and 'MyProblematicAlias' (aka 'Float')) are expected to be equal}}
cfAlias = otherAlias // expected-error {{cannot assign value of type 'MyProblematicAlias?' (aka 'Optional<Float>') to type 'MyProblematicAliasRef?' (aka 'Optional<MyProblematicObjectRef>')}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('MyProblematicAlias' (aka 'Float') and 'MyProblematicAliasRef' (aka 'MyProblematicObjectRef')) are expected to be equal}}

func isOptionalFloat(_: inout Optional<Float>) {}
isOptionalFloat(&otherAlias) // okay
Expand Down
1 change: 1 addition & 0 deletions test/ClangImporter/ctypes_parse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ func testFunctionPointers() {

useFunctionPointer2(anotherFP)
sizedFP = fp // expected-error {{cannot assign value of type 'fptr?' (aka 'Optional<@convention(c) (Int32) -> Int32>') to type '(@convention(c) (CInt, CInt, UnsafeMutableRawPointer?) -> Void)?'}}
// expected-note@-1 {{arguments to generic parameter 'Wrapped' ('fptr' (aka '@convention(c) (Int32) -> Int32') and '@convention(c) (CInt, CInt, UnsafeMutableRawPointer?) -> Void'}}
}

func testStructDefaultInit() {
Expand Down
Loading