Skip to content

Commit f314599

Browse files
Merge remote-tracking branch 'origin/main' into c-api
2 parents dcb5973 + ca412fc commit f314599

27 files changed

Lines changed: 2484 additions & 4178 deletions

.gitattributes

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,14 @@ Lib/venv/scripts/posix/* text eol=lf
5858
#
5959
[attr]generated linguist-generated=true diff=generated
6060

61-
Lib/_opcode_metadata.py generated
62-
Lib/keyword.py generated
63-
Lib/idlelib/help.html generated
64-
Lib/test/certdata/*.pem generated
65-
Lib/test/certdata/*.0 generated
66-
Lib/test/levenshtein_examples.json generated
67-
Lib/test/test_stable_abi_ctypes.py generated
68-
Lib/token.py generated
61+
Lib/_opcode_metadata.py generated
62+
Lib/keyword.py generated
63+
Lib/idlelib/help.html generated
64+
Lib/test/certdata/*.pem generated
65+
Lib/test/certdata/*.0 generated
66+
Lib/test/levenshtein_examples.json generated
67+
Lib/test/test_stable_abi_ctypes.py generated
68+
Lib/token.py generated
69+
crates/compiler-core/src/bytecode/opcode_metadata.rs generated
6970

70-
.github/workflows/*.lock.yml linguist-generated=true merge=ours
71+
.github/workflows/*.lock.yml linguist-generated=true merge=ours

.github/workflows/ci.yaml

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,8 @@ jobs:
519519
security-events: write # for zizmor
520520
steps:
521521
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
522+
with:
523+
persist-credentials: false
522524

523525
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
524526

@@ -544,12 +546,35 @@ jobs:
544546
package-manager-cache: false
545547
node-version: "24"
546548

547-
- name: prek
549+
- name: install prek
548550
id: prek
549551
uses: j178/prek-action@bdca6f102f98e2b4c7029491a53dfd366469e33d # v2.0.4
550552
with:
551553
cache: false
552554
show-verbose-logs: false
555+
install-only: true
556+
557+
- name: prek run
558+
run: prek run --show-diff-on-failure --color=always --all-files
559+
560+
- name: Get target CPython version
561+
id: cpython-version
562+
run: |
563+
version=$(cat .python-version)
564+
echo "version=${version}" >> "$GITHUB_OUTPUT"
565+
566+
- name: Clone CPython
567+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
568+
with:
569+
repository: python/cpython
570+
path: cpython
571+
ref: "v${{ steps.cpython-version.outputs.version }}"
572+
persist-credentials: false
573+
574+
- name: prek run (manual stage)
575+
run: prek run --show-diff-on-failure --color=always --all-files --hook-stage manual
576+
env:
577+
CPYTHON_ROOT: ${{ github.workspace }}/cpython
553578

554579
- name: save prek cache
555580
if: ${{ github.ref == 'refs/heads/main' }} # only save on main

.pre-commit-config.yaml

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,27 @@ repos:
4040
types: [rust]
4141
priority: 0
4242

43-
- id: generate-opcode-metadata
44-
name: generate opcode metadata
45-
entry: python scripts/generate_opcode_metadata.py
46-
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|scripts/generate_opcode_metadata\.py)$'
43+
- id: generate-rs-opcode-metadata
44+
name: generate rust opcode metadata
45+
entry: python tools/opcode_metadata/generate_rs_opcode_metadata.py
46+
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
4747
pass_filenames: false
4848
language: system
4949
require_serial: true
5050
priority: 1 # so rustfmt runs first
51+
stages:
52+
- manual
53+
54+
- id: generate-py-opcode-metadata
55+
name: generate python opcode metadata
56+
entry: python tools/opcode_metadata/generate_py_opcode_metadata.py
57+
files: '^(crates/compiler-core/src/bytecode/instruction\.rs|tools/opcode_metadata/*)$'
58+
pass_filenames: false
59+
language: system
60+
require_serial: true
61+
priority: 1 # so rustfmt runs first
62+
stages:
63+
- manual
5164

5265
- repo: https://github.com/streetsidesoftware/cspell-cli
5366
rev: v10.0.0

Lib/_opcode_metadata.py

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/test/test_json/test_decode.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ def test_float(self):
1818
self.assertIsInstance(rval, float)
1919
self.assertEqual(rval, 1.0)
2020

21-
@unittest.skip("TODO: RUSTPYTHON; called `Result::unwrap()` on an `Err` value: ParseFloatError { kind: Invalid }")
2221
def test_nonascii_digits_rejected(self):
2322
# JSON specifies only ascii digits, see gh-125687
2423
for num in ["1\uff10", "0.\uff10", "0e\uff10"]:

Lib/test/test_json/test_fail.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,4 @@ def test_linecol(self):
239239
(line, col, idx))
240240

241241
class TestPyFail(TestFail, PyTest): pass
242-
class TestCFail(TestFail, CTest):
243-
@unittest.expectedFailure # TODO: RUSTPYTHON
244-
def test_failures(self):
245-
return super().test_failures()
242+
class TestCFail(TestFail, CTest): pass

Lib/test/test_sqlite3/test_dbapi.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1444,7 +1444,6 @@ def test_blob_get_item_error_bigint(self):
14441444
with self.assertRaisesRegex(IndexError, "cannot fit 'int'"):
14451445
self.blob[_testcapi.ULLONG_MAX]
14461446

1447-
@unittest.expectedFailure # TODO: RUSTPYTHON
14481447
def test_blob_set_item_error(self):
14491448
with self.assertRaisesRegex(TypeError, "cannot be interpreted"):
14501449
self.blob[0] = b"multiple"

crates/capi/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ license.workspace = true
1212
crate-type = ["cdylib", "rlib"]
1313

1414
[dependencies]
15-
bitflags.workspace = true
15+
bitflags = { workspace = true }
1616
num-complex = { workspace = true }
1717
rustpython-vm = { workspace = true, features = ["threading", "compiler"] }
1818
rustpython-stdlib = {workspace = true, features = ["threading"] }

crates/capi/src/methodobject.rs

Lines changed: 74 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub union PyMethodPointer {
3030
) -> *mut PyObject,
3131
pub PyCFunctionFast: unsafe extern "C" fn(
3232
slf: *mut PyObject,
33-
args: *mut *mut PyObject,
33+
args: *const *mut PyObject,
3434
nargs: isize,
3535
) -> *mut PyObject,
3636
pub PyCFunctionFastWithKeywords: unsafe extern "C" fn(
@@ -45,24 +45,26 @@ pub(crate) fn build_method_def(
4545
vm: &VirtualMachine,
4646
ml: &PyMethodDef,
4747
has_self: bool,
48-
) -> PyRef<HeapMethodDef> {
48+
) -> PyResult<PyRef<HeapMethodDef>> {
4949
let name = unsafe { CStr::from_ptr(ml.ml_name) }
5050
.to_str()
51-
.expect("Method name was not valid UTF-8");
51+
.map_err(|_| vm.new_system_error("Method name was not valid UTF-8"))?;
5252

53-
let doc = NonNull::new(ml.ml_doc.cast_mut()).map(|doc| {
54-
unsafe { CStr::from_ptr(doc.as_ptr()) }
55-
.to_str()
56-
.expect("Method doc was not valid UTF-8")
57-
});
53+
let doc = NonNull::new(ml.ml_doc.cast_mut())
54+
.map(|doc| {
55+
unsafe { CStr::from_ptr(doc.as_ptr()) }
56+
.to_str()
57+
.map_err(|_| vm.new_system_error("Method doc was not valid UTF-8"))
58+
})
59+
.transpose()?;
5860

59-
let flags =
60-
PyMethodFlags::from_bits(ml.ml_flags as u32).expect("PyMethodDef contains unknown flags");
61+
let flags = PyMethodFlags::from_bits(ml.ml_flags as u32)
62+
.ok_or_else(|| vm.new_system_error("PyMethodDef contains unknown flags"))?;
6163

6264
let method = ml.ml_meth;
6365

6466
if flags.contains(PyMethodFlags::METHOD) {
65-
panic!("METH_METHOD is not supported on abi3")
67+
return Err(vm.new_system_error("METH_METHOD is not supported on abi3"));
6668
}
6769

6870
let call_flags = flags
@@ -76,39 +78,43 @@ pub(crate) fn build_method_def(
7678
PyMethodFlags::NOARGS => {
7779
if has_self {
7880
let callable = move |zelf: PyObjectRef, vm: &VirtualMachine| unsafe {
79-
call_function(vm, method, flags, Some(vec![zelf]))
81+
let f = method.PyCFunction;
82+
let ret_ptr = f(zelf.as_raw().cast_mut(), core::ptr::null_mut());
83+
ret_ptr_to_pyresult(vm, ret_ptr)
8084
};
81-
vm.ctx.new_method_def(name, callable, flags, doc)
85+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
8286
} else {
8387
let callable = move |vm: &VirtualMachine| unsafe {
84-
call_function::<FuncArgs>(vm, method, flags, None)
88+
let f = method.PyCFunction;
89+
let ret_ptr = f(core::ptr::null_mut(), core::ptr::null_mut());
90+
ret_ptr_to_pyresult(vm, ret_ptr)
8591
};
86-
vm.ctx.new_method_def(name, callable, flags, doc)
92+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
8793
}
8894
},
8995
PyMethodFlags::VARARGS => {
9096
let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe {
9197
call_function(vm, method, flags, Some(args))
9298
};
93-
vm.ctx.new_method_def(name, callable, flags, doc)
99+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
94100
},
95101
PyMethodFlags::VARARGS | PyMethodFlags::KEYWORDS => {
96102
let callable = move | args: FuncArgs, vm: &VirtualMachine| unsafe {
97103
call_function_with_keywords(vm, method, flags, args)
98104
};
99-
vm.ctx.new_method_def(name, callable, flags, doc)
105+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
100106
},
101107
PyMethodFlags::FASTCALL | PyMethodFlags::KEYWORDS => {
102108
let callable = move |args: FuncArgs, vm: &VirtualMachine| unsafe {
103109
call_fast_function_with_keywords(vm, method, flags, args)
104110
};
105-
vm.ctx.new_method_def(name, callable, flags, doc)
111+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
106112
},
107113
PyMethodFlags::FASTCALL => {
108-
let callable = move |_args: PosArgs, _vm: &VirtualMachine| -> PyResult {
109-
todo!("METH_FASTCALL without METH_KEYWORDS is not supported yet")
114+
let callable = move |args: PosArgs, vm: &VirtualMachine| unsafe {
115+
call_fast_function(vm, method, flags, args)
110116
};
111-
vm.ctx.new_method_def(name, callable, flags, doc)
117+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
112118
},
113119
PyMethodFlags::O => {
114120
let f = unsafe { method.PyCFunction };
@@ -117,17 +123,19 @@ pub(crate) fn build_method_def(
117123
let ret_ptr = unsafe { f(zelf.as_raw().cast_mut(), arg.as_raw().cast_mut()) };
118124
ret_ptr_to_pyresult(vm, ret_ptr)
119125
};
120-
vm.ctx.new_method_def(name, callable, flags, doc)
126+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
121127
} else {
122128
let callable = move |arg: PyObjectRef, vm: &VirtualMachine| -> PyResult {
123-
let ret_ptr = unsafe { f(std::ptr::null_mut(), arg.as_raw().cast_mut()) };
129+
let ret_ptr = unsafe { f(core::ptr::null_mut(), arg.as_raw().cast_mut()) };
124130
ret_ptr_to_pyresult(vm, ret_ptr)
125131
};
126-
vm.ctx.new_method_def(name, callable, flags, doc)
132+
Ok(vm.ctx.new_method_def(name, callable, flags, doc))
127133
}
128134
},
129135
_ => {
130-
todo!("function {name} has unsupported or invalid calling-convention flags: {flags:?}")
136+
Err(vm.new_system_error(format!(
137+
"function {name} has unsupported or invalid calling-convention flags: {flags:?}"
138+
)))
131139
},
132140
})
133141
}
@@ -202,31 +210,46 @@ unsafe fn call_fast_function_with_keywords(
202210
.unwrap_or_default();
203211
let nargs = args.args.len();
204212
let mut fastcall_args = args.args;
205-
let mut kwnames_tuple = None;
206-
if !args.kwargs.is_empty() {
213+
let kwnames_tuple = if !args.kwargs.is_empty() {
207214
let mut kwnames = Vec::with_capacity(args.kwargs.len());
208215
for (k, v) in args.kwargs {
209216
kwnames.push(vm.ctx.new_str(k).into());
210217
fastcall_args.push(v);
211218
}
212-
kwnames_tuple = Some(vm.ctx.new_tuple(kwnames));
213-
}
214-
let fastcall_arg_ptrs = fastcall_args
215-
.iter()
216-
.map(|obj| obj.as_object().as_raw().cast_mut())
217-
.collect::<Vec<_>>();
219+
Some(vm.ctx.new_tuple(kwnames))
220+
} else {
221+
None
222+
};
218223
let kwnames_ptr = kwnames_tuple
219224
.as_ref()
220225
.map(|tuple| tuple.as_object().as_raw().cast_mut())
221-
.unwrap_or(core::ptr::null_mut());
222-
let ret_ptr = unsafe {
223-
f(
224-
slf_ptr,
225-
fastcall_arg_ptrs.as_ptr(),
226-
nargs as isize,
227-
kwnames_ptr,
228-
)
229-
};
226+
.unwrap_or_default();
227+
// SAFETY: PyObjectRef is repr(transparent) over a pointer to PyObject, so a
228+
// Vec<PyObjectRef> has a layout-compatible contiguous backing buffer. The
229+
// vector is kept alive for the duration of the call.
230+
let fastcall_arg_ptrs = fastcall_args.as_ptr().cast::<*mut PyObject>();
231+
let ret_ptr = unsafe { f(slf_ptr, fastcall_arg_ptrs, nargs as isize, kwnames_ptr) };
232+
ret_ptr_to_pyresult(vm, ret_ptr)
233+
}
234+
235+
unsafe fn call_fast_function(
236+
vm: &VirtualMachine,
237+
method: PyMethodPointer,
238+
flags: PyMethodFlags,
239+
args: PosArgs,
240+
) -> PyResult {
241+
let f = unsafe { method.PyCFunctionFast };
242+
let mut args: FuncArgs = args.into();
243+
let slf = take_self_arg(&mut args, flags);
244+
let slf_ptr = slf
245+
.as_ref()
246+
.map(|obj| obj.as_object().as_raw().cast_mut())
247+
.unwrap_or_default();
248+
// SAFETY: PyObjectRef is repr(transparent) over a pointer to PyObject, so a
249+
// Vec<PyObjectRef> has a layout-compatible contiguous backing buffer. The
250+
// vector is kept alive for the duration of the call.
251+
let fastcall_arg_ptrs = args.args.as_mut_ptr().cast::<*mut PyObject>();
252+
let ret_ptr = unsafe { f(slf_ptr, fastcall_arg_ptrs, args.args.len() as isize) };
230253
ret_ptr_to_pyresult(vm, ret_ptr)
231254
}
232255

@@ -247,7 +270,7 @@ fn take_self_arg(args: &mut FuncArgs, flags: PyMethodFlags) -> Option<PyObjectRe
247270
}
248271

249272
#[unsafe(no_mangle)]
250-
pub extern "C" fn PyCMethod_New(
273+
pub unsafe extern "C" fn PyCMethod_New(
251274
ml: *mut PyMethodDef,
252275
slf: *mut PyObject,
253276
_module: *mut PyObject,
@@ -260,24 +283,27 @@ pub extern "C" fn PyCMethod_New(
260283
);
261284
let ml = unsafe { &*ml };
262285
let zelf = unsafe { slf.as_ref().map(|obj| obj.to_owned()) };
263-
Ok(build_method_def(vm, ml, zelf.is_some())
286+
Ok(build_method_def(vm, ml, zelf.is_some())?
264287
.build_function(vm, zelf)
265288
.into())
266289
})
267290
}
268291

269292
#[unsafe(no_mangle)]
270-
pub extern "C" fn PyCFunction_New(ml: *mut PyMethodDef, slf: *mut PyObject) -> *mut PyObject {
271-
PyCMethod_New(ml, slf, core::ptr::null_mut(), core::ptr::null_mut())
293+
pub unsafe extern "C" fn PyCFunction_New(
294+
ml: *mut PyMethodDef,
295+
slf: *mut PyObject,
296+
) -> *mut PyObject {
297+
unsafe { PyCMethod_New(ml, slf, core::ptr::null_mut(), core::ptr::null_mut()) }
272298
}
273299

274300
#[unsafe(no_mangle)]
275-
pub extern "C" fn PyCFunction_NewEx(
301+
pub unsafe extern "C" fn PyCFunction_NewEx(
276302
ml: *mut PyMethodDef,
277303
slf: *mut PyObject,
278304
module: *mut PyObject,
279305
) -> *mut PyObject {
280-
PyCMethod_New(ml, slf, module, core::ptr::null_mut())
306+
unsafe { PyCMethod_New(ml, slf, module, core::ptr::null_mut()) }
281307
}
282308

283309
#[cfg(test)]

0 commit comments

Comments
 (0)