Skip to content

Commit c0055e7

Browse files
Merge pull request #33380 from LucianoPAlmeida/SR-13359-subscript-tuple
[SR-13359] [Sema] Tailored diagnostics for missing subscript members on tuple type base
2 parents 7de9573 + d62e9dd commit c0055e7

File tree

5 files changed

+137
-0
lines changed

5 files changed

+137
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ ERROR(could_not_find_subscript_member_did_you_mean,none,
8888
"did you mean to use the subscript operator?",
8989
(Type))
9090

91+
ERROR(could_not_find_subscript_member_tuple, none,
92+
"cannot access element using subscript for tuple type %0; "
93+
"use '.' notation instead", (Type))
94+
ERROR(could_not_find_subscript_member_tuple_did_you_mean_use_dot, none,
95+
"cannot access element using subscript for tuple type %0; "
96+
"did you mean to use '.%1'?", (Type, StringRef))
97+
9198
ERROR(could_not_find_enum_case,none,
9299
"enum type %0 has no case %1; did you mean %2?",
93100
(Type, DeclNameRef, DeclName))

lib/Sema/CSDiagnostics.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3212,6 +3212,9 @@ bool MissingMemberFailure::diagnoseAsError() {
32123212
if (diagnoseInLiteralCollectionContext())
32133213
return true;
32143214

3215+
if (diagnoseForSubscriptMemberWithTupleBase())
3216+
return true;
3217+
32153218
auto baseType = resolveType(getBaseType())->getWithoutSpecifierType();
32163219

32173220
DeclNameLoc nameLoc(::getLoc(anchor));
@@ -3428,6 +3431,73 @@ bool MissingMemberFailure::diagnoseInLiteralCollectionContext() const {
34283431
return false;
34293432
}
34303433

3434+
bool MissingMemberFailure::diagnoseForSubscriptMemberWithTupleBase() const {
3435+
auto locator = getLocator();
3436+
auto baseType = resolveType(getBaseType())->getWithoutSpecifierType();
3437+
3438+
auto *SE = getAsExpr<SubscriptExpr>(locator->getAnchor());
3439+
if (!SE)
3440+
return false;
3441+
3442+
auto tupleType = baseType->getAs<TupleType>();
3443+
// For non-tuple type or empty tuples, let's fallback to the general
3444+
// diagnostic logic.
3445+
if (!tupleType || tupleType->getNumElements() == 0)
3446+
return false;
3447+
3448+
auto *index = SE->getIndex();
3449+
if (SE->getNumArguments() == 1) {
3450+
auto *literal =
3451+
dyn_cast<IntegerLiteralExpr>(index->getSemanticsProvidingExpr());
3452+
3453+
llvm::Regex NumericRegex("^[0-9]+$");
3454+
// Literal expressions may have other types of representations e.g. 0x01,
3455+
// 0b01. So let's make sure to only suggest this tailored literal fix-it for
3456+
// number only literals.
3457+
if (literal && NumericRegex.match(literal->getDigitsText())) {
3458+
unsigned int literalValue = 0;
3459+
literal->getDigitsText().getAsInteger(/*Radix=*/0, literalValue);
3460+
3461+
// Verify if the literal value is within the bounds of tuple elements.
3462+
if (!literal->isNegative() &&
3463+
literalValue < tupleType->getNumElements()) {
3464+
llvm::SmallString<4> dotAccess;
3465+
llvm::raw_svector_ostream OS(dotAccess);
3466+
OS << "." << literalValue;
3467+
3468+
emitDiagnostic(
3469+
diag::could_not_find_subscript_member_tuple_did_you_mean_use_dot,
3470+
baseType, literal->getDigitsText())
3471+
.fixItReplace(index->getSourceRange(), OS.str());
3472+
return true;
3473+
}
3474+
}
3475+
3476+
// For subscript access on tuple base types where the subscript index is a
3477+
// string literal expression which value matches a tuple element label,
3478+
// let's suggest tuple label access.
3479+
auto stringLiteral =
3480+
dyn_cast<StringLiteralExpr>(index->getSemanticsProvidingExpr());
3481+
if (stringLiteral && !stringLiteral->getValue().empty() &&
3482+
llvm::any_of(tupleType->getElements(), [&](TupleTypeElt element) {
3483+
return element.getName().is(stringLiteral->getValue());
3484+
})) {
3485+
llvm::SmallString<16> dotAccess;
3486+
llvm::raw_svector_ostream OS(dotAccess);
3487+
OS << "." << stringLiteral->getValue();
3488+
3489+
emitDiagnostic(
3490+
diag::could_not_find_subscript_member_tuple_did_you_mean_use_dot,
3491+
baseType, stringLiteral->getValue())
3492+
.fixItReplace(index->getSourceRange(), OS.str());
3493+
return true;
3494+
}
3495+
}
3496+
3497+
emitDiagnostic(diag::could_not_find_subscript_member_tuple, baseType);
3498+
return true;
3499+
}
3500+
34313501
bool UnintendedExtraGenericParamMemberFailure::diagnoseAsError() {
34323502
MissingMemberFailure::diagnoseAsError();
34333503

lib/Sema/CSDiagnostics.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,14 @@ class MissingMemberFailure : public InvalidMemberRefFailure {
10751075
/// that defaults the element type. e.g. _ = [.e]
10761076
bool diagnoseInLiteralCollectionContext() const;
10771077

1078+
/// Tailored diagnostics for missing subscript member on a tuple base type.
1079+
/// e.g
1080+
/// ```swift
1081+
/// let tuple: (Int, Int) = (0, 0)
1082+
/// _ = tuple[0] // -> tuple.0.
1083+
/// ```
1084+
bool diagnoseForSubscriptMemberWithTupleBase() const;
1085+
10781086
static DeclName findCorrectEnumCaseName(Type Ty,
10791087
TypoCorrectionResults &corrections,
10801088
DeclNameRef memberName);

localization/diagnostics/en.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,6 +2967,14 @@
29672967
value of type %0 has no property or method named 'subscript'; did you
29682968
mean to use the subscript operator?
29692969
2970+
- id: could_not_find_subscript_member_tuple
2971+
msg: >-
2972+
cannot access element using subscript for tuple type %0; use '.' notation instead
2973+
2974+
- id: could_not_find_subscript_member_tuple_did_you_mean_use_dot
2975+
msg: >-
2976+
cannot access element using subscript for tuple type %0; did you mean to use '.%1'?
2977+
29702978
- id: could_not_find_enum_case
29712979
msg: >-
29722980
enum type %0 has no case %1; did you mean %2?

test/Constraints/members.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,3 +674,47 @@ _ = [.e: 1] // expected-error {{reference to member 'e' cannot be resolved witho
674674
let _ : [Int: Any] = [1 : .e] // expected-error {{type 'Any' has no member 'e'}}
675675
let _ : (Int, Any) = (1, .e) // expected-error {{type 'Any' has no member 'e'}}
676676
_ = (1, .e) // expected-error {{cannot infer contextual base in reference to member 'e'}}
677+
678+
// SR-13359
679+
typealias Pair = (Int, Int)
680+
func testSR13359(_ pair: (Int, Int), _ alias: Pair, _ void: Void, labeled: (a: Int, b: Int)) {
681+
_ = pair[0] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; did you mean to use '.0'?}} {{11-14=.0}}
682+
_ = pair[1] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; did you mean to use '.1'?}} {{11-14=.1}}
683+
_ = pair[2] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
684+
_ = pair[100] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
685+
_ = pair["strting"] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
686+
_ = pair[-1] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
687+
_ = pair[1, 1] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
688+
_ = void[0] // expected-error {{value of type 'Void' has no subscripts}}
689+
// Other representations of literals
690+
_ = pair[0x00] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
691+
_ = pair[0b00] // expected-error {{cannot access element using subscript for tuple type '(Int, Int)'; use '.' notation instead}} {{none}}
692+
693+
_ = alias[0] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); did you mean to use '.0'?}} {{12-15=.0}}
694+
_ = alias[1] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); did you mean to use '.1'?}} {{12-15=.1}}
695+
_ = alias[2] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
696+
_ = alias[100] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
697+
_ = alias["strting"] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
698+
_ = alias[-1] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
699+
_ = alias[1, 1] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
700+
_ = alias[0x00] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
701+
_ = alias[0b00] // expected-error {{cannot access element using subscript for tuple type 'Pair' (aka '(Int, Int)'); use '.' notation instead}} {{none}}
702+
703+
// Labeled tuple base
704+
_ = labeled[0] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; did you mean to use '.0'?}} {{14-17=.0}}
705+
_ = labeled[1] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; did you mean to use '.1'?}} {{14-17=.1}}
706+
_ = labeled[2] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
707+
_ = labeled[100] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
708+
_ = labeled["strting"] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
709+
_ = labeled[-1] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
710+
_ = labeled[1, 1] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
711+
_ = labeled[0x00] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
712+
_ = labeled[0b00] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
713+
714+
// Suggesting use label access
715+
_ = labeled["a"] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; did you mean to use '.a'?}} {{14-19=.a}}
716+
_ = labeled["b"] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; did you mean to use '.b'?}} {{14-19=.b}}
717+
_ = labeled["c"] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
718+
_ = labeled[""] // expected-error {{cannot access element using subscript for tuple type '(a: Int, b: Int)'; use '.' notation instead}} {{none}}
719+
720+
}

0 commit comments

Comments
 (0)