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
[SR-8089] Fix-it misplaces .self if name of type is wrapped in String interpolation parentheses #50622
Comments
brentdax (JIRA User), CodaFi (JIRA User): this seems related to the work on avoiding tuple expressions in interpolations. Got time to give Matthew some pointers? |
Where I would start on debugging this is: What does the AST look like right now, and which node are we looking at when we diagnose the error? To answer the first question, put a minimal test case in a file and run "path/to/swift -frontend -dump-ast test.swift", where "test.swift" is your test case's file. What you should see (along with some other stuff representing the rest of the file) is an interpolated_string_literal_expr whose children alternate between string_literal_expr and paren_expr (with the paren_exprs having children representing the expressions inside them). Sometimes you don't end up with a ParenExpr, though, and that tends to cause weird little bugs. To answer the second question, set a breakpoint in MiscDiagnostics and run your test file with the debugger attached to Swift; then when it hits your breakpoint, type "expr E->dump()" in the debugger. (Or stick "E->dump();" in the code and recompile. Ugly and slow, but you don't have to wrestle with the debugger.) This should print out the node we're looking at in the same format that the -dump-ast switch uses. |
Comment by Matthew Rips (JIRA) Thanks, Jordan. Appreciate the pointers, Brent. In the AST, the paren_expr is missing. It is resolving to: type_expr type='(MyClass).Type' I'm inspired, and will chase it down. |
Matt Rips (JIRA User), if you're still trying to chase this down, I would look at parseStringSegments() in ParseExpr.cpp. Specifically, if you skip down to the Lexer::StringSegment::Expr case and look a couple dozen lines down, you'll see a call to parseExprList(). That's where string interpolations are parsed. You might want to drop a breakpoint there and see if the parentheses are already part of the type at that point. If so, we can patch it up there; if not, we can try to figure out where it gets changed afterwards. |
Comment by Matthew Rips (JIRA) Brent Royal-Gordon, sorry to not respond sooner. I was engaged in a combination of setting up my Swift development environment (so that I could take the steps that you describe!) and learning more about the the type-checking system (like reading Doug Gregor's write up from 2012-14 and reading the code) and the Swift architecture. I'm setup, now, and know enough to perhaps be useful. Thank you for these additional pointers. I'll report back, shortly, with findings. |
Comment by Matthew Rips (JIRA) Brent Royal-Gordon, I've tracked down the mechanism at work, here. The parens and the type name are folded together during type checking, as part of an ASTWalk initiated in the preCheckExpression method of TypeChecker. Eventually, the simplifyTypeExpr method of PreCheckExpression (an ASTWalker) is called. The specific portion of that method that does the folding is: /// Simplify expressions which are type sugar productions that got parsed
/// as expressions due to the parser not knowing which identifiers are
/// type names.
TypeExpr *PreCheckExpression::simplifyTypeExpr(Expr *E) {
...
// Fold (T) into a type T with parens around it.
if (auto *PE = dyn_cast<ParenExpr>(E)) {
auto *TyExpr = dyn_cast<TypeExpr>(PE->getSubExpr());
if (!TyExpr) return nullptr;
TupleTypeReprElement InnerTypeRepr[] = { TyExpr->getTypeRepr() };
assert(!TyExpr->isImplicit() && InnerTypeRepr[0].Type &&
"SubscriptExpr doesn't work on implicit TypeExpr's, "
"the TypeExpr should have been built correctly in the first place");
auto *NewTypeRepr = TupleTypeRepr::create(TC.Context,
InnerTypeRepr,
PE->getSourceRange());
return new (TC.Context) TypeExpr(TypeLoc(NewTypeRepr, Type()));
}
...
} The comment leading into this method call suggests that this code is a patch to address some subtle issues earlier in the type check process; seemingly involving types not properly resolving as such. When the minimal example arrives at this point in the type checker, the type seems to be resolving as if it were properly formed. Specifically, using `let a = (Int)`, results in the relevant part of the AST being: (paren_expr type='<null>' (type_expr type='<null>' typerepr='Int')) Interestingly, the `Int` portion seems to be properly recognized as a type expression before the code, above, starts folding it together. After it leaves this section of code, the AST has it as: (type_expr type='<null>' typerepr='(Int)') So, what is the purpose behind this code transforming expressions that already seem to be properly formed? The comment at the beginning of the method suggests that it is trying to deal with a type declaration that has been mis-evaluated as an expression. But, here, of course, the minimal example is syntactically incorrect. We've omitted the `.self`, and the compiler eventually complains about it. When we add the `.self`, the code, above, leaves it alone, and the parentheses are not folded into the type. I'm going to continue to explore the mechanism at work, here. Wondering if you might have some thoughts about it. |
Matt Rips (JIRA User): Good job tracing this! I actually didn't think it was very likely that the problem was in type-checking, so you found it despite my hint, not because of it. Understanding what's happening here requires some knowledge of the compiler you probably don't have yet, so let me give you some background. The Swift AST represents arguments to a call as a ParenExpr if there is one unlabeled argument, or a TupleExpr if there are zero arguments, one labeled argument, or more than one argument (with or without labels). This is sort of a legacy thing; in earlier versions of Swift, we had the idea that functions only ever had one argument (which might be a tuple), but that ended up not really working out as the language evolved. We've gradually removed all evidence of that behavior from the language as users see it, but the AST currently still represents arguments that way. (This is a pet peeve of mine—at least once a week, I have to fight the impulse to open Expr.h and start writing an ArgumentListExpr.) Now, if you look at the first lines of 'simplifyTypeExpr()', you'll notice that there's a check which is supposed to exempt argument lists from the simplification it's doing. But it's not saving us here—probably because the check doesn't realize that the segments of an InterpolatedStringLiteralExpr are destined to become argument lists and need to be treated the same way. If you can figure out how to get it to treat an InterpolatedStringLiteralExpr's segments as argument lists, I think we'll be in business. Once you have a plan for how to fix this bug, I think these should be your next steps:
Hope this helps! |
Comment by Matthew Rips (JIRA) Brent Royal-Gordon: I'm afraid that I'd be putting a band-aid on a small symptom of a larger bug. The code that folds (T) is doing something that is fundamentally illegal. It is creating a single-element tuple, but there is no such thing as a single-element tuple. The documentation states that, "All tuple types contain two or more types, except for |
Matt Rips (JIRA User): Internally, Swift actually does use single-element tuples purposefully in some cases (mainly for single labeled arguments). But we know they sneak through sometimes when they shouldn't. The set of refactorings needed to fix it properly is too big to do immediately, but several of us are gearing up to wage war on the root causes of these problems. So apply your band-aid and bide your time. Honestly, if it actually fixes a single-element tuple instead of just limiting the damage, it's better than the tuple band-aid I wrote a couple weeks ago. |
Environment
Xcode 10.0 beta
Swift 4.2
Additional Detail from JIRA
md5: 655de83d0fb8a7d6e8aaaa9d0d58016a
Issue Description:
NOTE: This issue looks like an awkward corner case of the fix-it system. Since it isn't doing real harm, it probably is not worth the time to resolve it properly. There probably is a hack that would give a quick resolution, but that approach might have unintended side effects.
BACKGROUND: When correctly accessing a type as a value for purposes of String interpolation (or otherwise), .self is appended to the type name. Example:
If .self is omitted, the compiler reports an error and offers a fix-it.
ISSUE: Mashing the fix-button results in .self being appended. However, the issue is that, if the type name is wrapped within the String interpolation parentheses, then the .self is appended after the parentheses rather than to the type name.
Of course, the correct placement of the .self is immediately after the type name, as shown in the initial code snippet, above. With the .self placed outside the parentheses, a new error condition results.
Partial Diagnosis:
I don't know enough about the compiler code base and C++ to resolve the issue, but it looks to me like the issue centers on the code at:
lib/Sema/MiscDiagnostics.cpp at line 825:
It appears that the call to canAppendPostfixExpression() is returning true. Inside the canAppendPostfixExpression() function (in file lib/AST/Expr.cpp at line 683), the return value switches on the result of a call to getKind(), and in the case of ExprKind::InterpolatedStringLiteral, the return value is true. But, I'm not even sure that that is the case to which this sort of expression resolves. It seems to me that, given the error reported, the expression must be resolving to something like a MetaType, but I don't yet know enough about the compiler's approach to resolving expressions. So, my investigation ends there.
If this issue (and its potential resolution) is of a simple enough nature to qualify as a starter bug, I would be game to do the learning necessary to resolve it.
The text was updated successfully, but these errors were encountered: