Skip to content

Commit

Permalink
[Diagnostics] When replacing an existential parameter type with a typ…
Browse files Browse the repository at this point in the history
…e parameter,

insert 'some' instead of an explicit type parameter.

Replacing 'any' with 'some' allows the code to compile without further
changes, such as naming an explicit type parameter, and is future-proofed
for same-type requirements on primary associated types instead of needing
additional logic to add a 'where' clause.
  • Loading branch information
hborla committed Apr 16, 2022
1 parent a2328b0 commit 976addc
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 97 deletions.
97 changes: 36 additions & 61 deletions lib/Sema/CSDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3972,8 +3972,11 @@ bool InvalidMemberRefOnExistential::diagnoseAsError() {

// If the base expression is a reference to a function or subscript
// parameter, offer a fixit that replaces the existential parameter type with
// its generic equivalent, e.g. func foo(p: any P) → func foo<T: P>(p: T).
// FIXME: Add an option to use 'some' vs. an explicit generic parameter.
// its generic equivalent, e.g. func foo(p: any P) → func foo(p: some P).
// Replacing 'any' with 'some' allows the code to compile without further
// changes, such as naming an explicit type parameter, and is future-proofed
// for same-type requirements on primary associated types instead of needing
// a where clause.

if (!PD || !PD->getDeclContext()->getAsDecl())
return true;
Expand Down Expand Up @@ -4022,70 +4025,42 @@ bool InvalidMemberRefOnExistential::diagnoseAsError() {
if (PD->isInOut())
return true;

constexpr StringRef GPNamePlaceholder = "<#generic parameter name#>";
SourceRange TyReplacementRange;
SourceRange RemoveAnyRange;
SourceLoc GPDeclLoc;
std::string GPDeclStr;
{
llvm::raw_string_ostream OS(GPDeclStr);
auto *const GC = PD->getDeclContext()->getAsDecl()->getAsGenericContext();
if (GC->getParsedGenericParams()) {
GPDeclLoc = GC->getParsedGenericParams()->getRAngleLoc();
OS << ", ";
} else {
GPDeclLoc =
isa<AbstractFunctionDecl>(GC)
? cast<AbstractFunctionDecl>(GC)->getParameters()->getLParenLoc()
: cast<SubscriptDecl>(GC)->getIndices()->getLParenLoc();
OS << "<";
}
OS << GPNamePlaceholder << ": ";

auto *TR = PD->getTypeRepr()->getWithoutParens();
if (auto *STR = dyn_cast<SpecifierTypeRepr>(TR)) {
TR = STR->getBase()->getWithoutParens();
}
if (auto *ETR = dyn_cast<ExistentialTypeRepr>(TR)) {
TR = ETR->getConstraint();
RemoveAnyRange = SourceRange(ETR->getAnyLoc(), TR->getStartLoc());
TR = TR->getWithoutParens();
}
if (auto *MTR = dyn_cast<MetatypeTypeRepr>(TR)) {
TR = MTR->getBase();

// (P & Q).Type -> T.Type
// (P).Type -> (T).Type
// ((P & Q)).Type -> ((T)).Type
if (auto *TTR = dyn_cast<TupleTypeRepr>(TR)) {
assert(TTR->isParenType());
if (!isa<CompositionTypeRepr>(TTR->getElementType(0))) {
TR = TR->getWithoutParens();
}
}
}
TyReplacementRange = TR->getSourceRange();

// Strip any remaining parentheses and print the conformance constraint.
TR->getWithoutParens()->print(OS);

if (!GC->getParsedGenericParams()) {
OS << ">";
}
auto *typeRepr = PD->getTypeRepr()->getWithoutParens();
if (auto *STR = dyn_cast<SpecifierTypeRepr>(typeRepr)) {
typeRepr = STR->getBase()->getWithoutParens();
}

// First, replace the constraint type with the generic parameter type
// placeholder.
Diag.fixItReplace(TyReplacementRange, GPNamePlaceholder);
SourceRange anyRange;
TypeRepr *constraintRepr = typeRepr;
if (auto *existentialRepr = dyn_cast<ExistentialTypeRepr>(typeRepr)) {
constraintRepr = existentialRepr->getConstraint()->getWithoutParens();
auto anyStart = existentialRepr->getAnyLoc();
auto anyEnd = existentialRepr->getConstraint()->getStartLoc();
anyRange = SourceRange(anyStart, anyEnd);
}

// Remove 'any' if needed, using a character-based removal to pick up
// whitespaces between it and its constraint repr.
if (RemoveAnyRange.isValid()) {
Diag.fixItRemoveChars(RemoveAnyRange.Start, RemoveAnyRange.End);
bool needsParens = false;
while (auto *metatype = dyn_cast<MetatypeTypeRepr>(constraintRepr)) {
// The generic equivalent of 'any P.Type' is '(some P).Type'
constraintRepr = metatype->getBase()->getWithoutParens();
if (isa<SimpleIdentTypeRepr>(constraintRepr))
needsParens = !isa<TupleTypeRepr>(metatype->getBase());
}

// Finally, insert the generic parameter declaration.
Diag.fixItInsert(GPDeclLoc, GPDeclStr);
std::string fix;
llvm::raw_string_ostream OS(fix);
if (needsParens)
OS << "(";
OS << "some ";
constraintRepr->print(OS);
if (needsParens)
OS << ")";

// When removing 'any', use a character-based removal to pick up
// whitespaces between it and its constraint repr.
Diag
.fixItReplace(constraintRepr->getSourceRange(), fix)
.fixItRemoveChars(anyRange.Start, anyRange.End);

return true;
}
Expand Down
Loading

0 comments on commit 976addc

Please sign in to comment.