Skip to content

[Diagnostics] Add a detailed diagnostic for generic argument mismatches #25060

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 14, 2019
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
7 changes: 7 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,13 @@ ERROR(cannot_convert_closure_result_protocol,none,
(Type, Type))
ERROR(cannot_convert_closure_result_nil,none,
"'nil' is not compatible with closure result type %0", (Type))
ERROR(cannot_convert_parent_type,none,
"cannot convert parent type %0 to expected type %1",
(Type, Type))

NOTE(generic_argument_mismatch,none,
"arguments to generic parameter %0 (%1 and %2) are expected to be equal",
(Identifier, Type, Type))

ERROR(destructor_not_accessible,none,
"deinitializers cannot be accessed", ())
Expand Down
111 changes: 111 additions & 0 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,117 @@ bool MissingConformanceFailure::diagnoseAsError() {
return RequirementFailure::diagnoseAsError();
}

Optional<Diag<Type, Type>> GenericArgumentsMismatchFailure::getDiagnosticFor(
ContextualTypePurpose context) {
switch (context) {
case CTP_Initialization:
case CTP_AssignSource:
return diag::cannot_convert_assign;
case CTP_ReturnStmt:
case CTP_ReturnSingleExpr:
return diag::cannot_convert_to_return_type;
case CTP_DefaultParameter:
return diag::cannot_convert_default_arg_value;
case CTP_YieldByValue:
return diag::cannot_convert_yield_value;
case CTP_CallArgument:
return diag::cannot_convert_argument_value;
case CTP_ClosureResult:
return diag::cannot_convert_closure_result;
case CTP_ArrayElement:
return diag::cannot_convert_array_element;
// TODO(diagnostics): Make dictionary related diagnostics take prescedence
// over CSDiag. Currently these won't ever be produced.
case CTP_DictionaryKey:
return diag::cannot_convert_dict_key;
case CTP_DictionaryValue:
return diag::cannot_convert_dict_value;
case CTP_CoerceOperand:
return diag::cannot_convert_coerce;
case CTP_SubscriptAssignSource:
return diag::cannot_convert_subscript_assign;

case CTP_ThrowStmt:
case CTP_Unused:
case CTP_CannotFail:
case CTP_YieldByReference:
case CTP_CalleeResult:
case CTP_EnumCaseRawValue:
break;
}
return None;
}

void GenericArgumentsMismatchFailure::emitNoteForMismatch(int position) {
auto genericTypeDecl = getActual()->getCanonicalType()->getAnyGeneric();
auto param = genericTypeDecl->getGenericParams()->getParams()[position];

auto lhs = resolveType(getActual()->getGenericArgs()[position])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can replace all of resolveType calls here with a single one for actual/expected and retrieve arguments from there since resolveType is going to recursively resolve all type variables.

->reconstituteSugar(/*recursive=*/false);
auto rhs = resolveType(getRequired()->getGenericArgs()[position])
->reconstituteSugar(/*recursive=*/false);

auto noteLocation = param->getLoc();

if (!noteLocation.isValid()) {
noteLocation = getAnchor()->getLoc();
}

emitDiagnostic(noteLocation, diag::generic_argument_mismatch,
param->getName(), lhs, rhs);
}

bool GenericArgumentsMismatchFailure::diagnoseAsError() {
auto *anchor = getAnchor();
auto path = getLocator()->getPath();

Optional<Diag<Type, Type>> diagnostic;
if (path.empty()) {
assert(isa<AssignExpr>(anchor));
diagnostic = getDiagnosticFor(CTP_AssignSource);
} else {
const auto &last = path.back();
switch (last.getKind()) {
case ConstraintLocator::ContextualType: {
auto purpose = getConstraintSystem().getContextualTypePurpose();
assert(purpose != CTP_Unused);
diagnostic = getDiagnosticFor(purpose);
break;
}

case ConstraintLocator::AutoclosureResult:
case ConstraintLocator::ApplyArgToParam:
case ConstraintLocator::ApplyArgument: {
diagnostic = diag::cannot_convert_argument_value;
break;
}

case ConstraintLocator::ParentType: {
diagnostic = diag::cannot_convert_parent_type;
break;
}

case ConstraintLocator::ClosureResult: {
diagnostic = diag::cannot_convert_closure_result;
break;
}

default:
break;
}
}

if (!diagnostic)
return false;

emitDiagnostic(
getAnchor()->getLoc(), *diagnostic,
resolveType(getActual())->reconstituteSugar(/*recursive=*/false),
resolveType(getRequired())->reconstituteSugar(/*recursive=*/false));
emitNotesForMismatches();
return true;
}

bool LabelingFailure::diagnoseAsError() {
auto &cs = getConstraintSystem();
auto *anchor = getRawAnchor();
Expand Down
41 changes: 41 additions & 0 deletions lib/Sema/CSDiagnostics.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class FailureDiagnostic {
bool diagnose(bool asNote = false);

/// Try to produce an error diagnostic for the problem at hand.
///
/// \returns true If anything was diagnosed, false otherwise.
virtual bool diagnoseAsError() = 0;

/// Instead of producing an error diagnostic, attempt to
Expand Down Expand Up @@ -359,6 +361,45 @@ class MissingConformanceFailure final : public RequirementFailure {
}
};

/// Diagnostics for mismatched generic arguments e.g
/// ```swift
/// struct F<G> {}
/// let _:F<Int> = F<Bool>()
/// ```
class GenericArgumentsMismatchFailure final : public FailureDiagnostic {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be based off of ContextualFailure.

BoundGenericType *Actual;
BoundGenericType *Required;
ArrayRef<unsigned> Mismatches;

public:
GenericArgumentsMismatchFailure(Expr *expr, ConstraintSystem &cs,
BoundGenericType *actual,
BoundGenericType *required,
ArrayRef<unsigned> mismatches,
ConstraintLocator *locator)
: FailureDiagnostic(expr, cs, locator), Actual(actual),
Required(required), Mismatches(mismatches) {}

bool diagnoseAsError() override;

private:
void emitNotesForMismatches() {
for (unsigned position : Mismatches) {
emitNoteForMismatch(position);
}
}

void emitNoteForMismatch(int mismatchPosition);

Optional<Diag<Type, Type>> getDiagnosticFor(ContextualTypePurpose context);

/// The actual type being used.
BoundGenericType *getActual() const { return Actual; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the base type is ContextualFailure these accessors wouldn't be needed.


/// The type needed by the generic requirement.
BoundGenericType *getRequired() const { return Required; }
};

/// Diagnose failures related to same-type generic requirements, e.g.
/// ```swift
/// protocol P {
Expand Down
17 changes: 17 additions & 0 deletions lib/Sema/CSFix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,23 @@ ContextualMismatch *ContextualMismatch::create(ConstraintSystem &cs, Type lhs,
return new (cs.getAllocator()) ContextualMismatch(cs, lhs, rhs, locator);
}

bool GenericArgumentsMismatch::diagnose(Expr *root, bool asNote) const {
auto failure = GenericArgumentsMismatchFailure(root, getConstraintSystem(),
getActual(), getRequired(),
getMismatches(), getLocator());
return failure.diagnose(asNote);
}

GenericArgumentsMismatch *GenericArgumentsMismatch::create(
ConstraintSystem &cs, BoundGenericType *actual, BoundGenericType *required,
llvm::ArrayRef<unsigned> mismatches, ConstraintLocator *locator) {
unsigned size = totalSizeToAlloc<unsigned>(mismatches.size());
void *mem =
cs.getAllocator().Allocate(size, alignof(GenericArgumentsMismatch));
return new (mem)
GenericArgumentsMismatch(cs, actual, required, mismatches, locator);
}

bool AutoClosureForwarding::diagnose(Expr *root, bool asNote) const {
auto failure =
AutoClosureForwardingFailure(getConstraintSystem(), getLocator());
Expand Down
60 changes: 59 additions & 1 deletion lib/Sema/CSFix.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ enum class FixKind : uint8_t {
/// like the types are aligned.
ContextualMismatch,

/// Fix up the generic arguments of two types so they match each other.
GenericArgumentsMismatch,

/// Fix up @autoclosure argument to the @autoclosure parameter,
/// to for a call to be able to foward it properly, since
/// @autoclosure conversions are unsupported starting from
Expand Down Expand Up @@ -164,7 +167,8 @@ enum class FixKind : uint8_t {
/// associated with single declaration.
ExplicitlySpecifyGenericArguments,

/// Skip any unhandled constructs that occur within a closure argument that matches up with a
/// Skip any unhandled constructs that occur within a closure argument that
/// matches up with a
/// parameter that has a function builder.
SkipUnhandledConstructInFunctionBuilder,
};
Expand Down Expand Up @@ -488,6 +492,60 @@ class ContextualMismatch : public ConstraintFix {
ConstraintLocator *locator);
};

/// Detect situations where two type's generic arguments must
/// match but are not convertible e.g.
///
/// ```swift
/// struct F<G> {}
/// let _:F<Int> = F<Bool>()
/// ```
class GenericArgumentsMismatch final
: public ConstraintFix,
private llvm::TrailingObjects<GenericArgumentsMismatch, unsigned> {
friend TrailingObjects;

BoundGenericType *Actual;
BoundGenericType *Required;

unsigned NumMismatches;

protected:
GenericArgumentsMismatch(ConstraintSystem &cs, BoundGenericType *actual,
BoundGenericType *required,
llvm::ArrayRef<unsigned> mismatches,
ConstraintLocator *locator)
: ConstraintFix(cs, FixKind::GenericArgumentsMismatch, locator),
Actual(actual), Required(required), NumMismatches(mismatches.size()) {
std::uninitialized_copy(mismatches.begin(), mismatches.end(),
getMismatchesBuf().begin());
}

public:
std::string getName() const override {
return "fix generic argument mismatch";
}

BoundGenericType *getActual() const { return Actual; }
BoundGenericType *getRequired() const { return Required; }

ArrayRef<unsigned> getMismatches() const {
return {getTrailingObjects<unsigned>(), NumMismatches};
}

bool diagnose(Expr *root, bool asNote = false) const override;

static GenericArgumentsMismatch *create(ConstraintSystem &cs,
BoundGenericType *actual,
BoundGenericType *required,
llvm::ArrayRef<unsigned> mismatches,
ConstraintLocator *locator);

private:
MutableArrayRef<unsigned> getMismatchesBuf() {
return {getTrailingObjects<unsigned>(), NumMismatches};
}
};

/// Detect situations where key path doesn't have capability required
/// by the context e.g. read-only vs. writable, or either root or value
/// types are incorrect e.g.
Expand Down
Loading