Repository navigation
WIP: Spurious substitution failure for lambda naming template parameter in signature [GH #338] #435
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20524,6 +20524,8 @@ TRUE. | |
| } /* substituted_cast_is_valid */ | ||
|
|
||
|
|
||
| static a_boolean lambda_body_is_instantiation_dependent(a_lambda_ptr lambda); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is a forward static declaration actually needed? Could we move the definition (and the functions it depends on) up here? |
||
|
|
||
| static an_expr_node_ptr copy_template_param_lambda_expr( | ||
| an_expr_node_ptr expr, | ||
| a_template_arg_ptr template_arg_list, | ||
|
|
@@ -20560,12 +20562,12 @@ copy_template_param_expr for the parameter descriptions. | |
| severe error cases). Substitution cannot proceed. */ | ||
| subst_fail(*copy_error); | ||
| } else { | ||
| an_operand result_operand; | ||
| an_expr_stack_entry expr_stack_entry; | ||
| an_expr_stack_entry_ptr saved_expr_stack; | ||
| a_boolean pushed_template_decl_scope = FALSE; | ||
| a_scope_ptr lambda_enclosing_scope = NULL; | ||
| a_scope_depth scope_depth_before_local_reactivation = | ||
| an_operand result_operand; | ||
| an_expr_stack_entry expr_stack_entry; | ||
| an_expr_stack_entry_ptr saved_expr_stack; | ||
| a_rescan_param_binding_state param_binding_state; | ||
| a_scope_ptr lambda_enclosing_scope = NULL; | ||
| a_scope_depth scope_depth_before_local_reactivation = | ||
| NO_SCOPE_DEPTH; | ||
| /* Set up a fresh expression and lexical context so that the lambda tokens | ||
| can be rescanned without interfering with the surrounding state, then | ||
|
|
@@ -20578,28 +20580,15 @@ copy_template_param_expr for the parameter descriptions. | |
| expr_stack_entry.suppress_diagnostics = TRUE; | ||
| expr_stack_entry.possible_rescan_context = TRUE; | ||
| push_lexical_state_stack(); | ||
| if (template_param_list != NULL && | ||
| template_param_list->cache.decl_info != NULL && | ||
| template_param_list->param_symbol != NULL) { | ||
| /* Make the template parameters being substituted visible during the | ||
| rescan so that names that refer to them can be resolved. The scope | ||
| is pushed reusing the original declaration scope number so that the | ||
| template parameter symbols are visible to ordinary name lookup. */ | ||
| push_template_declaration_scope_full( | ||
| template_param_list->cache.decl_info, | ||
| template_param_list->param_symbol->decl_scope, | ||
| /*is_template_template_param=*/FALSE, | ||
| /*is_template_param_rescan=*/FALSE); | ||
| scope_stack_top().is_reactivation = TRUE; | ||
| pushed_template_decl_scope = TRUE; | ||
| /* Force the template parameter symbols to refer to the values | ||
| specified by the actual template arguments so that name | ||
| lookup of those parameters during the rescan returns the | ||
| substituted values directly (their state will be restored | ||
| by the enclosing instantiation scope when it is left). */ | ||
| update_template_param_symbols(template_param_list, | ||
| template_arg_list); | ||
| } /* if */ | ||
| /* Bind any template parameters that have known arguments so that ordinary | ||
| name lookup during the rescan resolves them to their substituted values, | ||
| while leaving any not-yet-deduced parameters at their resting dependent | ||
| values. */ | ||
| begin_rescan_param_binding(template_param_list, template_arg_list, | ||
| /*push_decl_scope=*/TRUE, | ||
| /*enable_variadic=*/ | ||
| (options & CTWS_PRESERVE_DEDUCED_PACKS) == 0, | ||
| ¶m_binding_state); | ||
| /* If the lambda was originally declared in a local scope (i.e., inside a | ||
| function body), reactivate that scope so that local variables and other | ||
| names declared in the enclosing function are visible to ordinary name | ||
|
|
@@ -20649,13 +20638,28 @@ copy_template_param_expr for the parameter descriptions. | |
| scan_lambda_expression(&result_operand); | ||
| if (is_immediate_class_type(result_operand.type) && | ||
| is_lambda_closure_type(result_operand.type)) { | ||
| /* Pushing a template declaration scope above (to reactivate the | ||
| template parameters) unfortunately causes make_closure_class to mark | ||
| the closure as nonreal. In some situations with class template | ||
| argument deduction, we get a spuriously nonreal class even when we | ||
| didn't push the template declaration scope above. Just reset | ||
| the flag here. */ | ||
| result_operand.type->variant.class_struct_union.is_nonreal_class = FALSE; | ||
| /* In some situations with class template argument deduction, we get a | ||
| spuriously nonreal class. Reset the flag here unless the rescanned | ||
| lambda body still references un-substituted template parameters, and | ||
| associate the new closure class with the cached tokens in case a | ||
| subsequent substitution pass needs to rescan it again. */ | ||
| a_lambda_ptr new_lambda = NULL; | ||
| if (is_expression_operand(&result_operand) && | ||
| node_is(result_operand.variant.expression, enk_lambda)) { | ||
| new_lambda = | ||
| result_operand.variant.expression->variant.init.source.lambda; | ||
| } /* if */ | ||
| if (lambda_body_is_instantiation_dependent(new_lambda)) { | ||
| result_operand.type->variant.class_struct_union.is_nonreal_class = | ||
| TRUE; | ||
| new_lambda->lambda_routine->is_prototype_instantiation = TRUE; | ||
| } else { | ||
| result_operand.type->variant.class_struct_union.is_nonreal_class = | ||
| FALSE; | ||
| } /* if */ | ||
| if (cached_lambdas->get(result_operand.type) == NULL) { | ||
| cached_lambdas->map(result_operand.type, tokens); | ||
| } /* if */ | ||
| } /* if */ | ||
| flush_past_token_cache_terminator(); | ||
| if (scope_depth_before_local_reactivation != NO_SCOPE_DEPTH) { | ||
|
|
@@ -20664,9 +20668,7 @@ copy_template_param_expr for the parameter descriptions. | |
| pop_scope(); | ||
| } /* while */ | ||
| } /* if */ | ||
| if (pushed_template_decl_scope) { | ||
| pop_scope(); | ||
| } /* if */ | ||
| end_rescan_param_binding(¶m_binding_state); | ||
| pop_lexical_state_stack(); | ||
| pop_expr_stack(); | ||
| restore_expr_stack(saved_expr_stack); | ||
|
|
@@ -22519,6 +22521,8 @@ options. | |
| /* con_copy and constant are already set correctly. */ | ||
| } else if ((!cpp11_sfinae_enabled && !expr_copy->is_lvalue) || | ||
| (options & CTWS_IN_PARENT_SUBSTITUTION) != 0 || | ||
| ((options & CTWS_MAY_BE_RESCANNED) != 0 && | ||
| expr_is_instantiation_dependent(expr_copy)) || | ||
| keep_tpck_expression) { | ||
| /* The expression remains an expression. If the expression | ||
| changed, make a new tpck_expression constant for it. With | ||
|
|
@@ -27491,6 +27495,36 @@ indirectly by expr_is_instantiation_dependent. | |
| } /* examine_dyn_init_for_instantiation_dependence */ | ||
|
|
||
|
|
||
| static a_boolean lambda_body_is_instantiation_dependent(a_lambda_ptr lambda) | ||
| /* | ||
| Return TRUE if lambda is a non-generic lambda whose call operator body is | ||
| instantiation-dependent. | ||
| */ | ||
| { | ||
| a_boolean result = FALSE; | ||
|
|
||
| if (!C_mode() && lambda != NULL && !lambda->is_generic && | ||
| lambda->lambda_routine != NULL) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this also check that we're actually inside a templated context? |
||
| a_scope_ptr func_scope = | ||
| scope_for_routine_or_null(lambda->lambda_routine); | ||
| if (func_scope != NULL && func_scope->assoc_block != NULL) { | ||
| an_expr_or_stmt_traversal_block tblock; | ||
| clear_expr_or_stmt_traversal_block(&tblock); | ||
| tblock.process_expr = examine_expr_for_instantiation_dependence; | ||
| tblock.process_constant = examine_constant_for_instantiation_dependence; | ||
| tblock.process_dynamic_init = | ||
| examine_dyn_init_for_instantiation_dependence; | ||
| tblock.process_type = examine_type_for_instantiation_dependence; | ||
| tblock.process_non_dynamic_constants = TRUE; | ||
| tblock.skip_expr_process_type = TRUE; | ||
| traverse_statement(func_scope->assoc_block, &tblock); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inspect declarations when checking lambda-body dependence. If a lambda body contains only a dependent declaration, such as
|
||
| result = tblock.result; | ||
| } /* if */ | ||
| } /* if */ | ||
| return result; | ||
| } /* lambda_body_is_instantiation_dependent */ | ||
|
|
||
|
|
||
| a_boolean expr_is_instantiation_dependent(an_expr_node_ptr expr) | ||
| /* | ||
| Return TRUE if expr is instantiation-dependent. This normally includes type- | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| [GH #338] | ||
| Spurious substitution failure for lambda naming template parameter in signature | ||
|
|
||
| Previously, a lambda expression appearing in a function template signature | ||
| (or an enable_if attribute) that referenced an enclosing template parameter | ||
| failed template argument substitution. For example, with --c++20: | ||
|
|
||
| template<typename T> | ||
| void f(char (*)[sizeof([] { return T{}; }())]) {} | ||
| void g() { | ||
| char a[sizeof(int)]; | ||
| f<int>(&a); // Previously a spurious error, now okay. | ||
| } |
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.
Use the saved member position for the lookup.
locator->source_positionis uninitialized here.clear_locatorcopies it intoloc, so this introduces undefined behavior during closure rescans. Usequalified_member_position, which was initialized from the saved operand. (raw.githubusercontent.com)