Repository: python/cpython
Pull request: gh-123969: refactor _PyErr_RaiseSyntaxError and _PyErr_EmitSyntaxWarning out of compiler
These were local to the compiler, but after the splitting it would be good to be able to use them without needing to pass in a compiler struct (which is only used for the filename).

Fixes #123969.

<!--
Thanks for your contribution!
Please read this comment in its entirety. It's quite important.

# Pull Request title

It should be in the following format:

```
gh-NNNNN: Summary of the changes made
```

Where: gh-NNNNN refers to the GitHub issue number.

Most PRs will require an issue number. Trivial changes, like fixing a typo, do not need an issue.

# Backport Pull Request title

If this is a backport PR (PR made against branches other than `main`),
please ensure that the PR title is in the following format:

```
[X.Y] <title from the original PR> (GH-NNNN)
```

Where: [X.Y] is the branch name, e.g. [3.6].

GH-NNNN refers to the PR number from `main`.

-->


<!-- gh-issue-number: gh-123969 -->
* Issue: gh-123969
<!-- /gh-issue-number -->


Problem statement:
Add _PyErr_RaiseSyntaxError and _PyErr_EmitSyntaxWarning
The ``_PyCompile_Error`` and ``_PyCompile_Warn`` functions take a compiler struct as parameter, just for the filename. In order to be able to use these functions from areas of the codebase where this does not exist, we need versions that take just the filename. 

I think they should live in Python/errors.py, with `_PyErr_` prefix.



<!-- gh-linked-prs -->
### Linked PRs
* gh-123972
<!-- /gh-linked-prs -->

Hints from issue discussion:

Commit message: gh-123969: refactor _PyErr_RaiseSyntaxError and _PyErr_EmitSyntaxWarning out of compiler

Patch to review:
diff --git a/Include/internal/pycore_compile.h b/Include/internal/pycore_compile.h
index 9219d4d735a576..d4c8c5dd850799 100644
--- a/Include/internal/pycore_compile.h
+++ b/Include/internal/pycore_compile.h
@@ -31,8 +31,6 @@ extern int _PyCompile_AstOptimize(
     int optimize,
     struct _arena *arena);
 
-struct _Py_SourceLocation;
-
 extern int _PyAST_Optimize(
     struct _mod *,
     struct _arena *arena,
diff --git a/Include/internal/pycore_pyerrors.h b/Include/internal/pycore_pyerrors.h
index 9835e495d176e7..db61aa181a7b06 100644
--- a/Include/internal/pycore_pyerrors.h
+++ b/Include/internal/pycore_pyerrors.h
@@ -120,6 +120,11 @@ extern void _PyErr_SetNone(PyThreadState *tstate, PyObject *exception);
 
 extern PyObject* _PyErr_NoMemory(PyThreadState *tstate);
 
+extern int _PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset,
+                                    int end_lineno, int end_col_offset);
+extern int _PyErr_RaiseSyntaxError(PyObject *msg, PyObject *filename, int lineno, int col_offset,
+                                   int end_lineno, int end_col_offset);
+
 PyAPI_FUNC(void) _PyErr_SetString(
     PyThreadState *tstate,
     PyObject *exception,
diff --git a/Python/compile.c b/Python/compile.c
index 72399c758d51e8..3415cc857c5376 100644
--- a/Python/compile.c
+++ b/Python/compile.c
@@ -1112,27 +1112,15 @@ _PyCompile_Error(compiler *c, location loc, const char *format, ...)
     if (msg == NULL) {
         return ERROR;
     }
-    PyObject *loc_obj = PyErr_ProgramTextObject(c->c_filename, loc.lineno);
-    if (loc_obj == NULL) {
-        loc_obj = Py_None;
-    }
-    PyObject *args = Py_BuildValue("O(OiiOii)", msg, c->c_filename,
-                                   loc.lineno, loc.col_offset + 1, loc_obj,
-                                   loc.end_lineno, loc.end_col_offset + 1);
+    int ret = _PyErr_RaiseSyntaxError(msg, c->c_filename, loc.lineno, loc.col_offset,
+                                      loc.end_lineno, loc.end_col_offset);
     Py_DECREF(msg);
-    if (args == NULL) {
-        goto exit;
-    }
-    PyErr_SetObject(PyExc_SyntaxError, args);
- exit:
-    Py_DECREF(loc_obj);
-    Py_XDECREF(args);
-    return ERROR;
+    return ret;
 }
 
-/* Emits a SyntaxWarning and returns 1 on success.
+/* Emits a SyntaxWarning and returns 0 on success.
    If a SyntaxWarning raised as error, replaces it with a SyntaxError
-   and returns 0.
+   and returns -1.
 */
 int
 _PyCompile_Warn(compiler *c, location loc, const char *format, ...)
@@ -1144,21 +1132,10 @@ _PyCompile_Warn(compiler *c, location loc, const char *format, ...)
     if (msg == NULL) {
         return ERROR;
     }
-    if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, c->c_filename,
-                                 loc.lineno, NULL, NULL) < 0)
-    {
-        if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
-            /* Replace the SyntaxWarning exception with a SyntaxError
-               to get a more accurate error report */
-            PyErr_Clear();
-            assert(PyUnicode_AsUTF8(msg) != NULL);
-            _PyCompile_Error(c, loc, PyUnicode_AsUTF8(msg));
-        }
-        Py_DECREF(msg);
-        return ERROR;
-    }
+    int ret = _PyErr_EmitSyntaxWarning(msg, c->c_filename, loc.lineno, loc.col_offset,
+                                       loc.end_lineno, loc.end_col_offset);
     Py_DECREF(msg);
-    return SUCCESS;
+    return ret;
 }
 
 PyObject *
diff --git a/Python/errors.c b/Python/errors.c
index ad6b7dbef075cc..f50f416b027241 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -1850,6 +1850,55 @@ PyErr_SyntaxLocationEx(const char *filename, int lineno, int col_offset)
     Py_XDECREF(fileobj);
 }
 
+/* Raises a SyntaxError and returns -1.
+ * If something goes wrong, a different exception may be raised.
+ */
+
+int
+_PyErr_RaiseSyntaxError(PyObject *msg, PyObject *filename, int lineno, int col_offset,
+                        int end_lineno, int end_col_offset)
+{
+    PyObject *loc_obj = PyErr_ProgramTextObject(filename, lineno);
+    if (loc_obj == NULL) {
+        loc_obj = Py_None;
+    }
+    PyObject *args = Py_BuildValue("O(OiiOii)", msg, filename,
+                                   lineno, col_offset + 1, loc_obj,
+                                   end_lineno, end_col_offset + 1);
+    if (args == NULL) {
+        goto exit;
+    }
+    PyErr_SetObject(PyExc_SyntaxError, args);
+ exit:
+    Py_DECREF(loc_obj);
+    Py_XDECREF(args);
+    return -1;
+}
+
+/* Emits a SyntaxWarning and returns 0 on success.
+   If a SyntaxWarning is raised as error, replaces it with a SyntaxError
+   and returns -1.
+*/
+int
+_PyErr_EmitSyntaxWarning(PyObject *msg, PyObject *filename, int lineno, int col_offset,
+                         int end_lineno, int end_col_offset)
+{
+    if (PyErr_WarnExplicitObject(PyExc_SyntaxWarning, msg, filename,
+                                 lineno, NULL, NULL) < 0)
+    {
+        if (PyErr_ExceptionMatches(PyExc_SyntaxWarning)) {
+            /* Replace the SyntaxWarning exception with a SyntaxError
+               to get a more accurate error report */
+            PyErr_Clear();
+            assert(PyUnicode_AsUTF8(msg) != NULL);
+            _PyErr_RaiseSyntaxError(msg, filename, lineno, col_offset,
+                                    end_lineno, end_col_offset);
+        }
+        return -1;
+    }
+    return 0;
+}
+
 /* Attempt to load the line of text that the exception refers to.  If it
    fails, it will return NULL but will not set an exception.
 


Code review comments:
diff_hunk: @@ -1850,6 +1850,55 @@ PyErr_SyntaxLocationEx(const char *filename, int lineno, int col_offset)
     Py_XDECREF(fileobj);
 }
 
+/* Raises a SyntaxError and returns -1.
+ * If something goes wrong, a different exception may be raised.
+ */
+
path: Python/errors.c
text: @user1:
Should this newline be here?
```suggestion
```

Though, I think most editors will interpreter this as the docstring for `_PyErr_RaiseSyntaxError` anyway.