-
Notifications
You must be signed in to change notification settings - Fork 12.6k
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
Local types #3266
Local types #3266
Conversation
// Type parameters of a function are in scope in the entire function declaration, including the parameter | ||
// list and return type. However, local types are only in scope in the function body. | ||
if (!(meaning & SymbolFlags.Type) || !(result.flags & (SymbolFlags.Type & ~SymbolFlags.TypeParameter)) || | ||
!isFunctionLike(location) || lastLocation === (<FunctionLikeDeclaration>location).body) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather this be split into more if
s than cram the conditions into one boolean expression.
Alternatively, break each subexpression onto its own line and explain each of them independently. For instance:
// We've found something in the 'locals' table, but we are sensitive to location in whether it is valid to use
// this entity.
// - A non-type entity (i.e. values, namespaces, and aliases) is unconditionally resolved.
// - A type parameters are visible throughout the entirety of a declaration, and are resolved.
// - A local type found in any declaration *other than a function* can be considered resolved.
// - A local types found in a function is only resolved if it was used *within the body of the function*.
if (!(meaning & SymbolFlags.Type) ||
!(result.flags & (SymbolFlags.Type & ~SymbolFlags.TypeParameter)) ||
!isFunctionLike(location) ||
lastLocation === (<FunctionLikeDeclaration>location).body) {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general I think it is perfectly fine (and often preferable) to have multi-line boolean expressions instead of multiple if
statements with "explaining variables". The latter just make the code even more complicated to look at. There's nothing inherently simpler about multiple if
statements compared to multiple boolean operands separated by &&
and ||
. Quite the opposite actually, since with boolean expressions you know there aren't any side effects or other oddities you have to worry about.
I do agree that in this case putting each condition on a separate line might make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I disagree. I think that multiple if statements are actually simpler. They allow you to examine each clause, one at a time. They let each clause be simple explained. They do not require you to think through multiple different forms of boolean expressions in each clause.
Some minds (mine included) tend to have the problem where we see one expression, and then 'stamp' what we saw there on the next in a complicated clause. So if one expression does ||
and the next does &&
, or one uses !
while the other does not, then it's very easy to 'trip' over the expression, forcing the need to go back and scan and understand the expression over and over again.
At this point, we've been using this code for around a year at this point, and there are still comments/complaints on the PRs about the clarity of the code. At this point, i think we should accept that these constructs are difficult for many team members to read, and we should err on the side of making the code as maintainable as possible for the team as a whole.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll just have to disagree here. If the team has problems reading multi-line boolean expressions then I think we have a bigger problem. You're advocating turning simple and functional boolean expressions into multiple imperative if statements with temporary variables. I think that is exactly the wrong way to go. Whenever I see those I always wonder if I'm missing something subtle (which imperative code is full of), because otherwise it would just be written as a simple boolean expression.
nextToken(); | ||
return token === SyntaxKind.EnumKeyword | ||
function isStatement(): boolean { | ||
return (getStatementFlags() & StatementFlags.Statement) !== 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am confused about the purpose of the getStatementFlags approach. Why is it better than having isStatement and isModuleElement just make the decisions for themselves? Is it so that there can be just one place (instead of two) to account for all the node kinds here, so we don't forget any?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is modifiers. When modifiers are present we can't tell from the first token what we're looking at. So we need to look ahead, but we don't want to do that more than once. And, as you say, when everything is in one place we're certain that there won't be any subtle differences.
In terms of the parser, I prefer the approach where we indiscriminately parse statements and module elements, and subsequently error in the checker if you have a module element in a block context. Since we may eventually allow module declarations inside functions, that work may prove useful to us anyway. But I defer to you, and otherwise, I sign off. 👍 |
Since we've moved from For instance: function f<FT>(x: FT) {
class C<CT> {
private y: CT;
}
return C;
}
let con = f(10);
let obj = new con</**/ Do we only see inner type params? This would be a good fourslash test. |
@@ -3908,7 +4003,7 @@ module ts { | |||
return mapper(<TypeParameter>type); | |||
} | |||
if (type.flags & TypeFlags.Anonymous) { | |||
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ? | |||
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this for anonymous class expressions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it's for the anonymous type created for the static side of a class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, because previously the static side of a class could never get instantiated.
Writing the types looks good 👍 |
@DanielRosenwasser We do not have signature help for type arguments, only arguments. However, we do show the type arguments when we are giving sig help for the arguments, so this would be a good thing to test. |
This PR implements support for local class, interface, enum, and type alias declarations. For example:
Local types are block scoped, similar to variables declared with
let
andconst
. For example:The inferred return type of a function may be a type declared locally within the function. It is not possible for callers of the function to reference such a local type, but it can of course be matched structurally. For example:
Local types may reference enclosing type parameters and local class and interfaces may themselves be generic. For example:
Fixes #3217.
Fixes #2148.