diff --git a/src/quick-lint-js/fe/parse-type.cpp b/src/quick-lint-js/fe/parse-type.cpp index 0efa1b9858..c70f991ad8 100644 --- a/src/quick-lint-js/fe/parse-type.cpp +++ b/src/quick-lint-js/fe/parse-type.cpp @@ -90,31 +90,68 @@ void Parser::parse_and_visit_typescript_type_expression( if (this->peek().type == Token_Type::kw_readonly) { // readonly Type[] // readonly [Type, Type] - readonly_keyword = this->peek().span(); + // readonly is Type + Lexer_Transaction transaction = this->lexer_.begin_transaction(); + Identifier readonly = this->peek().identifier_name(); this->skip(); + + if (parse_options.allow_type_predicate && + this->peek().type == Token_Type::kw_is) { + // readonly is Type + this->lexer_.roll_back_transaction(std::move(transaction)); + goto type_variable_or_namespace_or_type_predicate; + } + this->lexer_.commit_transaction(std::move(transaction)); + readonly_keyword = readonly.span(); } switch (this->peek().type) { case Token_Type::complete_template: + case Token_Type::kw_false: + case Token_Type::kw_null: + case Token_Type::kw_true: + case Token_Type::kw_void: + case Token_Type::number: + case Token_Type::string: + this->skip(); + break; + + // any + // any is Type case Token_Type::kw_any: case Token_Type::kw_bigint: case Token_Type::kw_boolean: - case Token_Type::kw_false: case Token_Type::kw_never: - case Token_Type::kw_null: case Token_Type::kw_number: case Token_Type::kw_object: case Token_Type::kw_string: case Token_Type::kw_symbol: - case Token_Type::kw_this: - case Token_Type::kw_true: case Token_Type::kw_undefined: - case Token_Type::kw_unknown: - case Token_Type::kw_void: - case Token_Type::number: - case Token_Type::string: + case Token_Type::kw_unknown: { + Lexer_Transaction transaction = this->lexer_.begin_transaction(); + this->skip(); + if (this->peek().type == Token_Type::kw_is) { + // param is Type + this->lexer_.roll_back_transaction(std::move(transaction)); + goto type_variable_or_namespace_or_type_predicate; + } + this->lexer_.commit_transaction(std::move(transaction)); + break; + } + + // this + // this is Type + case Token_Type::kw_this: { + Lexer_Transaction transaction = this->lexer_.begin_transaction(); this->skip(); + if (this->peek().type == Token_Type::kw_is) { + // this is Type + this->lexer_.roll_back_transaction(std::move(transaction)); + goto type_variable_or_namespace_or_type_predicate; + } + this->lexer_.commit_transaction(std::move(transaction)); break; + } // `template ${sometype}` case Token_Type::incomplete_template: @@ -134,6 +171,8 @@ void Parser::parse_and_visit_typescript_type_expression( // Type // ns.Type + // param is Type + type_variable_or_namespace_or_type_predicate: case Token_Type::kw_abstract: case Token_Type::kw_accessor: case Token_Type::kw_as: @@ -168,10 +207,24 @@ void Parser::parse_and_visit_typescript_type_expression( case Token_Type::kw_type: case Token_Type::kw_yield: case Token_Type::identifier: { + Token_Type name_type = this->peek().type; Identifier name = this->peek().identifier_name(); - bool had_dot = false; this->skip(); + if (this->peek().type == Token_Type::kw_is) { + // param is Type + // this is Type + this->skip(); + if (name_type != Token_Type::kw_this) { + // this is Type + v.visit_variable_type_predicate_use(name); + } + this->parse_and_visit_typescript_type_expression(v); + return; + } + + bool had_dot = false; while (this->peek().type == Token_Type::dot) { + // ns.Type had_dot = true; this->skip(); switch (this->peek().type) { @@ -215,6 +268,7 @@ void Parser::parse_and_visit_typescript_type_expression( // T extends infer U ? V : W // T extends infer U extends X ? V : W case Token_Type::kw_infer: { + Lexer_Transaction transaction = this->lexer_.begin_transaction(); Source_Code_Span infer_keyword_span = this->peek().span(); this->skip(); switch (this->peek().type) { @@ -232,7 +286,6 @@ void Parser::parse_and_visit_typescript_type_expression( case Token_Type::kw_global: case Token_Type::kw_infer: case Token_Type::kw_intrinsic: - case Token_Type::kw_is: case Token_Type::kw_keyof: case Token_Type::kw_module: case Token_Type::kw_namespace: @@ -248,10 +301,21 @@ void Parser::parse_and_visit_typescript_type_expression( case Token_Type::kw_unique: break; + // infer is // 'is' is the declared name. + // infer is Type // Type predicate where 'infer' is the parameter name. + case Token_Type::kw_is: + if (parse_options.allow_type_predicate) { + // infer is Type + this->lexer_.roll_back_transaction(std::move(transaction)); + goto type_variable_or_namespace_or_type_predicate; + } + break; + default: QLJS_PARSER_UNIMPLEMENTED(); break; } + this->lexer_.commit_transaction(std::move(transaction)); Identifier variable = this->peek().identifier_name(); this->skip(); @@ -288,13 +352,22 @@ void Parser::parse_and_visit_typescript_type_expression( } // unique - // unique.prop // unique symbol - case Token_Type::kw_unique: + // unique is Type + case Token_Type::kw_unique: { + Lexer_Transaction transaction = this->lexer_.begin_transaction(); this->skip(); + if (parse_options.allow_type_predicate && + this->peek().type == Token_Type::kw_is) { + // unique is Type + this->lexer_.roll_back_transaction(std::move(transaction)); + goto type_variable_or_namespace_or_type_predicate; + } + this->lexer_.commit_transaction(std::move(transaction)); QLJS_PARSER_UNIMPLEMENTED_IF_NOT_TOKEN(Token_Type::kw_symbol); this->skip(); break; + } // [A, B, C] case Token_Type::left_square: @@ -477,10 +550,21 @@ void Parser::parse_and_visit_typescript_type_expression( break; // keyof Type - case Token_Type::kw_keyof: + // keyof is Type + case Token_Type::kw_keyof: { + Lexer_Transaction transaction = this->lexer_.begin_transaction(); this->skip(); + if (parse_options.allow_type_predicate && + this->peek().type == Token_Type::kw_is) { + // keyof is Type + this->lexer_.roll_back_transaction(std::move(transaction)); + goto type_variable_or_namespace_or_type_predicate; + } + // keyof Type + this->lexer_.commit_transaction(std::move(transaction)); this->parse_and_visit_typescript_type_expression(v, parse_options); break; + } // import("module").Name case Token_Type::kw_import: { @@ -620,48 +704,11 @@ void Parser::parse_and_visit_typescript_colon_type_expression_or_type_predicate( void Parser::parse_and_visit_typescript_type_expression_or_type_predicate( Parse_Visitor_Base &v, bool allow_parenthesized_type) { - switch (this->peek().type) { - // param is Type - // Type - QLJS_CASE_CONTEXTUAL_KEYWORD: - QLJS_CASE_STRICT_ONLY_RESERVED_KEYWORD: - case Token_Type::identifier: - case Token_Type::kw_await: - case Token_Type::kw_this: - case Token_Type::kw_yield: { - Token_Type parameter_type = this->peek().type; - Identifier parameter_name = this->peek().identifier_name(); - Lexer_Transaction transaction = this->lexer_.begin_transaction(); - this->skip(); - if (this->peek().type == Token_Type::kw_is) { - // param is Type - this->lexer_.commit_transaction(std::move(transaction)); - this->skip(); - if (parameter_type != Token_Type::kw_this) { - v.visit_variable_type_predicate_use(parameter_name); - } - this->parse_and_visit_typescript_type_expression(v); - } else { - // Type - this->lexer_.roll_back_transaction(std::move(transaction)); - this->parse_and_visit_typescript_type_expression(v); - } - break; - } - - // {key: Value} - // typeof v - // (param, param) => ReturnType - // ((((param, param) => ReturnType))) - // // Only if allow_parenthesized_type: - // (typeexpr) - default: - this->parse_and_visit_typescript_type_expression( - v, TypeScript_Type_Parse_Options{ - .allow_parenthesized_type = allow_parenthesized_type, - }); - break; - } + this->parse_and_visit_typescript_type_expression( + v, TypeScript_Type_Parse_Options{ + .allow_parenthesized_type = allow_parenthesized_type, + .allow_type_predicate = true, + }); } void Parser::parse_and_visit_typescript_arrow_type_expression( diff --git a/src/quick-lint-js/fe/parse.h b/src/quick-lint-js/fe/parse.h index 2a70b300c6..7d83392d41 100644 --- a/src/quick-lint-js/fe/parse.h +++ b/src/quick-lint-js/fe/parse.h @@ -211,6 +211,8 @@ class Parser { std::optional type_being_declared = std::nullopt; bool parse_question_as_invalid = true; bool allow_parenthesized_type = true; + // If false, a type predicate is parsed but a diagnostic is reported. + bool allow_type_predicate = false; }; void parse_and_visit_typescript_colon_type_expression(Parse_Visitor_Base &v);