[3.10] bpo-44885: Correct the ast locations of f-strings with format … by pablogsal · Pull Request #27743 · python/cpython
`parent` is the enclosing node. `expr_start` is the starting position of the expression (pointing to the open brace). `n` is the node which locations are going to be fixed relative to parent. `expr_str` is the child node's string representation, including braces. */ static bool fstring_find_expr_location(Token *parent, char *expr_str, int *p_lines, int *p_cols) fstring_find_expr_location(Token *parent, const char* expr_start, char *expr_str, int *p_lines, int *p_cols) { *p_lines = 0; *p_cols = 0; assert(expr_start != NULL && *expr_start == '{'); if (parent && parent->bytes) { const char *parent_str = PyBytes_AsString(parent->bytes); if (!parent_str) { return false; } const char *substr = strstr(parent_str, expr_str); if (substr) { // The following is needed, in order to correctly shift the column // offset, in the case that (disregarding any whitespace) a newline // immediately follows the opening curly brace of the fstring expression. bool newline_after_brace = 1; const char *start = substr + 1; while (start && *start != '}' && *start != '\n') { if (*start != ' ' && *start != '\t' && *start != '\f') { newline_after_brace = 0; break; } start++; // The following is needed, in order to correctly shift the column // offset, in the case that (disregarding any whitespace) a newline // immediately follows the opening curly brace of the fstring expression. bool newline_after_brace = 1; const char *start = expr_start + 1; while (start && *start != '}' && *start != '\n') { if (*start != ' ' && *start != '\t' && *start != '\f') { newline_after_brace = 0; break; } start++; }
// Account for the characters from the last newline character to our // left until the beginning of substr. if (!newline_after_brace) { start = substr; while (start > parent_str && *start != '\n') { start--; } *p_cols += (int)(substr - start); // Account for the characters from the last newline character to our // left until the beginning of expr_start. if (!newline_after_brace) { start = expr_start; while (start > parent_str && *start != '\n') { start--; } /* adjust the start based on the number of newlines encountered before the f-string expression */ for (const char *p = parent_str; p < substr; p++) { if (*p == '\n') { (*p_lines)++; } *p_cols += (int)(expr_start - start); } /* adjust the start based on the number of newlines encountered before the f-string expression */ for (const char *p = parent_str; p < expr_start; p++) { if (*p == '\n') { (*p_lines)++; } } }
len = expr_end - expr_start; /* Allocate 3 extra bytes: open paren, close paren, null byte. */ str = PyMem_Malloc(len + 3); str = PyMem_Calloc(len + 3, sizeof(char)); if (str == NULL) { PyErr_NoMemory(); return NULL; }
// The call to fstring_find_expr_location is responsible for finding the column offset // the generated AST nodes need to be shifted to the right, which is equal to the number // of the f-string characters before the expression starts. In order to correctly compute // this offset, strstr gets called in fstring_find_expr_location which only succeeds // if curly braces appear before and after the f-string expression (exactly like they do // in the f-string itself), hence the following lines. str[0] = '{'; // of the f-string characters before the expression starts. memcpy(str+1, expr_start, len); str[len+1] = '}'; str[len+2] = 0;
int lines, cols; if (!fstring_find_expr_location(t, str, &lines, &cols)) { if (!fstring_find_expr_location(t, expr_start-1, str+1, &lines, &cols)) { PyMem_Free(str); return NULL; }