Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
6400ba8
spec 1731: open the Modules / imports test panel
tamnd Jun 14, 2026
d42b1b2
os: expose os.altsep (None on POSIX, '/' on Windows)
tamnd Jun 14, 2026
4540eff
stdlib: vendor modulefinder, pyclbr and zipapp from CPython 3.14
tamnd Jun 14, 2026
45fbf4d
io subclasses, SystemExit.code, chmod path-like
tamnd Jun 14, 2026
845fda8
imports: port _testmultiphase main module and vendor test_importlib
tamnd Jun 14, 2026
e50c2e2
importlib.util: add _find_spec_from_path
tamnd Jun 14, 2026
423544f
import: attach __spec__/__loader__/__cached__ to loaded modules
tamnd Jun 14, 2026
9d611fb
importlib: port the real ModuleSpec with parent/has_location/cached
tamnd Jun 14, 2026
6c9b764
runpy: import-system surface, SIGINT exit, namespace packages
tamnd Jun 14, 2026
5bfcd88
imports: fix windows build, lint, and PathLike subprocess args
tamnd Jun 14, 2026
d4e8d54
site: vendor site/_sitebuiltins and install interpreter builtins at s…
tamnd Jun 14, 2026
1770db0
dict: invalidate keys version on resize so LOAD_ATTR cache cannot rea…
tamnd Jun 14, 2026
e840155
module: delegate repr to importlib._module_repr and collect dict cycles
tamnd Jun 14, 2026
fa1e7f1
zlib: return crc32/adler32 as unsigned 32-bit ints
tamnd Jun 14, 2026
91ca411
zipimport: vendor Lib/zipimport.py and wire bootstrap helpers
tamnd Jun 14, 2026
25866e6
zlib: default Compress.flush() to Z_FINISH; route zip imports through…
tamnd Jun 14, 2026
f5f674d
import: namespace portion accumulation + sourceless .pyc directory loads
tamnd Jun 14, 2026
696a0e8
sys: report statically-linked modules in builtin_module_names; Import…
tamnd Jun 14, 2026
cd01ffb
testcapi: port _testcapi.config_get over a PyConfig_Get spec table
tamnd Jun 14, 2026
6c03251
func: incref __dict__ attribute reads in func_getattro
tamnd Jun 14, 2026
c03e550
spec 1731: record zipimport/test_module green and the live-finders bl…
tamnd Jun 14, 2026
b0d53fa
importlib: vendor the real package and make the finders live
tamnd Jun 14, 2026
71a10df
marshal: accept memoryview in loads buffer extraction
tamnd Jun 14, 2026
9abc209
import: consult custom sys.meta_path finders; classmethod bindings co…
tamnd Jun 14, 2026
bd1d907
imp: faithful module-shadowing diagnostics + config sys_path_0
tamnd Jun 14, 2026
0f9d10b
vm: resolve instrumented opcode before counting jump cache stride
tamnd Jun 14, 2026
15ebc3c
main: bind __main__.__builtins__ to the builtins module
tamnd Jun 14, 2026
e826338
io,os: drop redundant Go finalizer on borrowed file descriptors
tamnd Jun 14, 2026
01e9f40
imp: enforce case-sensitive module matching on case-insensitive files…
tamnd Jun 14, 2026
4f32aab
os,_imp: posix bytes environ, dynamic-module unmarshal, DirEntry.is_j…
tamnd Jun 14, 2026
565b893
test: vendor test.test_import.data fixtures for import gate
tamnd Jun 14, 2026
589c0b2
import: faithful relative-import package resolution
tamnd Jun 14, 2026
28d64ee
import: keep the failing module's frame in import tracebacks
tamnd Jun 14, 2026
a97a3ca
import: publish _frozen_importlib aliases for the bootstrap modules
tamnd Jun 14, 2026
924ef42
import: emit ImportWarning when a child cannot bind onto an unwritabl…
tamnd Jun 14, 2026
890aacb
import: bind vendored test package onto its parent so circular-submod…
tamnd Jun 15, 2026
4dc612e
sys: make version_info a named struct-sequence with major/minor/micro…
tamnd Jun 15, 2026
aa8dbd9
imp: write and read __pycache__ bytecode caches
tamnd Jun 15, 2026
bdf4c6d
import: route flat and package imports through _frozen_importlib.__im…
tamnd Jun 15, 2026
2e768d7
import: trim importlib frames from tracebacks via frozen co_filename
tamnd Jun 15, 2026
37942f1
vm: own the const reference in LOAD_CONST
tamnd Jun 15, 2026
543b2a6
import: re-stamp nested co_filename and add _thread._excepthook
tamnd Jun 15, 2026
d9dc3f6
import: align os/sys unit tests with posix.environ bytes keys and liv…
tamnd Jun 15, 2026
ffbb4cd
debug: trace _bootstrap_external resolution on windows
tamnd Jun 15, 2026
a8f0eed
winreg: register the constant surface so _bootstrap_external imports …
tamnd Jun 15, 2026
093146d
os: port _path_splitroot for the Windows import bootstrap
tamnd Jun 15, 2026
b093f9d
vm,os: lift splitroot helpers and instrumentation preamble out of line
tamnd Jun 15, 2026
c1ef530
sys: expose winver on Windows so site can build the user path
tamnd Jun 15, 2026
5d21ff6
TEMP DEBUG: probe Windows flat-module import at boot
tamnd Jun 15, 2026
d4bd63b
debug: move Windows import probe to post-site gate context
tamnd Jun 15, 2026
6840b14
os: register posixmodule under one platform name (nt on Windows, posi…
tamnd Jun 15, 2026
00b576d
os: translate Windows error codes to POSIX errno for OSError
tamnd Jun 15, 2026
efb780b
errno: use CRT errno values on Windows so OSError promotes correctly
tamnd Jun 15, 2026
0e58c47
errors: drive ErrnoSubclass test off the platform errno table
tamnd Jun 15, 2026
226d9cf
frozen: register __hello__/__phello__ test modules and honor frozen o…
tamnd Jun 15, 2026
49519a3
spec 1731: mark test_frozen green in audit table and checklist
tamnd Jun 15, 2026
ca2f9dd
frozen: fix marshalled spelling flagged by misspell linter
tamnd Jun 15, 2026
931b46a
io: accept any bytes-like object in BufferedWriter.write
tamnd Jun 15, 2026
d6fdaab
instance: drop inline-values fast path once __dict__ is exposed
tamnd Jun 15, 2026
4e956bc
module: install __dict__ getset on ModuleType so subclasses resolve i…
tamnd Jun 15, 2026
75737ef
_thread: release GIL while RLock.acquire blocks on the gate
tamnd Jun 15, 2026
bfbc54e
imp: survive a fresh importlib re-import without crashing the spec bu…
tamnd Jun 15, 2026
a3174ac
import: faithful object setattr, built-in module loaders, winreg plat…
tamnd Jun 15, 2026
8250423
os: carry nanosecond mtime/atime/ctime in stat_result; vendor _pyio
tamnd Jun 15, 2026
2734c7a
import: thread the raw fromlist object into _handle_fromlist
tamnd Jun 15, 2026
c89dae0
io: honor errors= in TextIOWrapper decoders and reject directories at…
tamnd Jun 15, 2026
5b988fd
thread: release the GIL in time.sleep and publish child ident before …
tamnd Jun 15, 2026
b6e17d2
imp: don't cache .pyc for the frozen importlib bootstrap modules
tamnd Jun 15, 2026
7b869f2
marshal: raise EOFError on truncated buffers
tamnd Jun 15, 2026
cd10d1a
frozen: register full test module table with alias origins
tamnd Jun 15, 2026
6982b4c
os: accept PathLike in symlink/readlink/link via path_converter
tamnd Jun 15, 2026
ce64822
os: fstat/isatty via fstat(2) instead of borrowing the fd in an os.File
tamnd Jun 16, 2026
efaf834
_testcapi: run_in_subinterp runners + builtins.RunInFreshNamespace
tamnd Jun 16, 2026
0633551
spec 1731: record test_import threaded-crash fix in the checklist
tamnd Jun 16, 2026
56e51b9
vm: fix currentImporter test call arity and a gocritic empty-string c…
tamnd Jun 16, 2026
59489d1
io: actually disarm the os.File close finalizer on borrowed fds
tamnd Jun 16, 2026
47107db
spec 1731: record the large-stack fd finalizer fix on the checklist
tamnd Jun 16, 2026
f57245b
os: annotate cross-arch unconvert in stat_linux fstat path
tamnd Jun 16, 2026
c540c7d
gopy: make the importlib bootstrap install idempotent
tamnd Jun 16, 2026
8811662
spec 1731: refresh the imports panel after re-audit
tamnd Jun 16, 2026
2ec1e4b
import: single-phase extension cache, circular import, _gcd_import hook
tamnd Jun 16, 2026
7f9337d
import: give each subinterpreter its own view of extension modules
tamnd Jun 16, 2026
360da9b
imp: tighten stub-file perms and fix analogue spelling for the lint gate
tamnd Jun 16, 2026
7237e3d
spec 1731: record test_import green and the remaining frozen-importli…
tamnd Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,13 @@ TECHNICAL_BRIEF.md
/compare-baseline
Tools/bytecodes_gen/bytecodes_gen
/bytecodes_gen

# test_zipimport scratch artifacts
junk*.zip
junk*/

ziptestmodule
ziptestmodule.py

# test_module_with_large_stack writes this into the cwd
longlist.py
27 changes: 26 additions & 1 deletion builtins/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func parseCompileArgs(args []objects.Object, kwargs map[string]objects.Object) (
return compileArgs{}, err
}
}
filename, err := stringArg(bound[1], "filename")
filename, err := compileFilenameArg(bound[1])
if err != nil {
return compileArgs{}, err
}
Expand Down Expand Up @@ -150,6 +150,31 @@ func parseCompileArgs(args []objects.Object, kwargs map[string]objects.Object) (
}, nil
}

// compileFilenameArg decodes the filename argument. compile() runs it
// through PyUnicode_FSDecoder, which accepts str, bytes, or any
// os.PathLike (pathlib.Path) by invoking __fspath__. importlib's source
// loaders pass a pathlib.Path here, so a bare str check is too strict.
//
// CPython: Python/bltinmodule.c builtin_compile (filename: object,
//
// PyUnicode_FSDecoder) and Objects/unicodeobject.c PyOS_FSPath
func compileFilenameArg(o objects.Object) (string, error) {
switch v := o.(type) {
case *objects.Unicode:
return v.Value(), nil
case *objects.Bytes:
return string(v.Bytes()), nil
}
if fspath, err := objects.GetAttr(o, objects.NewStr("__fspath__")); err == nil {
result, callErr := objects.CallNoArgs(fspath)
if callErr != nil {
return "", callErr
}
return compileFilenameArg(result)
}
return "", fmt.Errorf("TypeError: compile() filename must be str, bytes or os.PathLike, not %s", o.Type().Name)
}

// compileSourceArg accepts the first positional argument to compile().
// str routes through ParseString. bytes / bytearray route through
// ParseBytes so the PEP 263 coding cookie controls the decode. AST
Expand Down
20 changes: 20 additions & 0 deletions builtins/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,3 +329,23 @@ func runCode(code *objects.Code, globals, locals, closure objects.Object) (objec
}
return currentEvaluator(code, globals, locals, closure)
}

// RunInFreshNamespace compiles and runs source in a brand-new __main__
// namespace and returns PyRun_SimpleStringFlags's result code: 0 when the
// code runs to completion, -1 when it raises. It backs the subinterpreter
// test entries (_testcapi.run_in_subinterp and
// _testinternalcapi.run_in_subinterp_with_config). Every gopy extension is
// a Go builtin compiled into the runtime (multi-phase by construction), so
// importing one inside a subinterpreter behaves exactly like a fresh-
// namespace exec in the current process; the only observable result the
// callers read is the integer status.
//
// CPython: Python/pythonrun.c:592 PyRun_SimpleStringFlags
func RunInFreshNamespace(source string) int {
ns := objects.NewDict()
_ = ns.SetItem(objects.NewStr("__name__"), objects.NewStr("__main__"))
if _, err := Exec([]objects.Object{objects.NewStr(source), ns}, nil); err != nil {
return -1
}
return 0
}
71 changes: 24 additions & 47 deletions builtins/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,22 @@ import (
)

// Importer resolves a module by name, with pkgname as the anchor for
// relative imports and level as the dot-count. fromlist is empty for
// `import a.b.c` and non-empty for `from a.b import c, d`. The hook
// returns the resolved module along with the same chain CPython hands
// back: when fromlist is empty the caller wants the top-level package,
// when fromlist is non-empty the caller wants the deepest module so
// relative imports and level as the dot-count. fromlist is the raw
// object the caller passed (None for `import a.b.c`, a sequence for
// `from a.b import c, d`); it is handed to _handle_fromlist unchanged,
// so a non-str entry surfaces as the TypeError _handle_fromlist raises
// rather than an early gopy-only rejection, and an arbitrary iterable
// is iterated the same way CPython iterates it. globals is the dict the
// caller handed to __import__ (or nil); the live importlib re-derives
// the package anchor from it via _calc___package__, so it must be the
// caller's explicit globals, not the running frame's. The hook returns
// the resolved module along with the same chain CPython hands back:
// when fromlist is empty the caller wants the top-level package, when
// fromlist is non-empty the caller wants the deepest module so
// IMPORT_FROM can grab attributes off it.
//
// CPython: Python/import.c:1561 PyImport_ImportModuleLevelObject
type Importer func(name, pkgname string, level int, fromlist []string) (objects.Object, error)
type Importer func(name, pkgname string, level int, fromlist objects.Object, globals objects.Object) (objects.Object, error)

var currentImporter Importer

Expand Down Expand Up @@ -73,13 +80,13 @@ func Import(args []objects.Object, kwargs map[string]objects.Object) (objects.Ob
}
}
pkgname := pkgnameFromGlobals(parsed.globals)
return currentImporter(parsed.name, pkgname, parsed.level, parsed.fromlist)
return currentImporter(parsed.name, pkgname, parsed.level, parsed.fromlist, parsed.globals)
}

type importArgs struct {
name string
globals objects.Object
fromlist []string
fromlist objects.Object
level int
}

Expand Down Expand Up @@ -130,9 +137,15 @@ func parseImportArgs(args []objects.Object, kwargs map[string]objects.Object) (i
return importArgs{}, fmt.Errorf("ValueError: level must be >= 0")
}
}
fromlist, err := fromlistArg(bound[3])
if err != nil {
return importArgs{}, err
// fromlist reaches the import machinery untouched. CPython's
// builtin___import___impl performs no type or element check; an empty
// tuple stands in for a missing argument, and _handle_fromlist raises
// the TypeError for any non-str entry or iterates a custom iterable.
//
// CPython: Python/bltinmodule.c:259 builtin___import___impl
fromlist := bound[3]
if fromlist == nil {
fromlist = objects.NewTuple(nil)
}
return importArgs{
name: name,
Expand All @@ -142,42 +155,6 @@ func parseImportArgs(args []objects.Object, kwargs map[string]objects.Object) (i
}, nil
}

// fromlistArg unpacks the fromlist argument into a flat []string.
// None and missing both mean "empty"; a tuple or list is iterated; any
// other type is a TypeError. The element check matches CPython's
// import.c which rejects non-str entries before lookup.
//
// CPython: Python/import.c:1726 import_from
func fromlistArg(o objects.Object) ([]string, error) {
if o == nil || objects.IsNone(o) {
return nil, nil
}
var raw []objects.Object
switch v := o.(type) {
case *objects.Tuple:
raw = make([]objects.Object, v.Len())
for i := range raw {
raw[i] = v.Item(i)
}
case *objects.List:
raw = make([]objects.Object, v.Len())
for i := range raw {
raw[i] = v.Item(i)
}
default:
return nil, fmt.Errorf("TypeError: fromlist must be a tuple or list")
}
out := make([]string, 0, len(raw))
for _, item := range raw {
s, err := stringArg(item, "fromlist item")
if err != nil {
return nil, err
}
out = append(out, s)
}
return out, nil
}

// stringArg coerces o to a Go string, raising TypeError when o isn't a
// Python str. The label is the argument name used in the error.
func stringArg(o objects.Object, label string) (string, error) {
Expand Down
49 changes: 41 additions & 8 deletions builtins/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,40 @@ type importCall struct {
fromlist []string
}

// fromlistStrings flattens the raw fromlist object the hook now
// receives into the []string the assertions below compare against. It
// mirrors how _handle_fromlist iterates the object, stopping at the
// first non-str entry (none of these tests pass one).
func fromlistStrings(o objects.Object) []string {
var out []string
switch v := o.(type) {
case *objects.Tuple:
for i := 0; i < v.Len(); i++ {
if u, ok := v.Item(i).(*objects.Unicode); ok {
out = append(out, u.Value())
}
}
case *objects.List:
for i := 0; i < v.Len(); i++ {
if u, ok := v.Item(i).(*objects.Unicode); ok {
out = append(out, u.Value())
}
}
}
return out
}

func captureImporter(t *testing.T, mod objects.Object, returnErr error) *importCall {
t.Helper()
prev := currentImporter
t.Cleanup(func() { SetImporter(prev) })

got := &importCall{}
SetImporter(func(name, pkgname string, level int, fromlist []string) (objects.Object, error) {
SetImporter(func(name, pkgname string, level int, fromlist objects.Object, _ objects.Object) (objects.Object, error) {
got.name = name
got.pkgname = pkgname
got.level = level
got.fromlist = fromlist
got.fromlist = fromlistStrings(fromlist)
return mod, returnErr
})
return got
Expand Down Expand Up @@ -198,16 +221,26 @@ func TestImportNegativeLevel(t *testing.T) {
}
}

func TestImportFromlistRejectsString(t *testing.T) {
captureImporter(t, nil, nil)
_, err := Import([]objects.Object{
func TestImportFromlistPassesThroughRawObject(t *testing.T) {
// CPython's builtin___import__ never type-checks fromlist; it hands the
// object straight to _handle_fromlist, which iterates it. A str is a
// valid (if unusual) fromlist, so __import__ must not reject it early.
mod := objects.NewModule("a")
got := captureImporter(t, mod, nil)
out, err := Import([]objects.Object{
objects.NewStr("a"),
objects.None(),
objects.None(),
objects.NewStr("notalist"),
objects.NewStr("xy"),
}, nil)
if err == nil || !strings.Contains(err.Error(), "fromlist must be a tuple or list") {
t.Fatalf("__import__: err=%v, want fromlist TypeError", err)
if err != nil {
t.Fatalf("__import__: %v", err)
}
if out != mod {
t.Fatalf("__import__ returned %v, want %v", out, mod)
}
if got.name != "a" {
t.Fatalf("hook name = %q, want a", got.name)
}
}

Expand Down
14 changes: 14 additions & 0 deletions builtins/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ import (

var wireOnce sync.Once

// DefaultImport holds the interpreter's original __import__ builtin so the
// IMPORT_NAME fast path can recognize it by identity even after user code
// rebinds builtins.__import__.
//
// CPython: pycore_interp.h interp->imports.import_func
var DefaultImport objects.Object

// Init constructs the builtins dict and stamps the v0.6 surface into
// it: None / True / False / NotImplemented as named constants, and
// print as the single callable. defaultFile is the io.Writer the
Expand Down Expand Up @@ -151,6 +158,13 @@ func Init(defaultFile io.Writer) (*objects.Dict, error) {
if err := setBuiltin(dict, "__import__", importFn); err != nil {
return nil, err
}
// Capture the interpreter's original __import__ so the IMPORT_NAME fast
// path can compare against it by identity. Re-reading the builtins
// module is wrong: a test that swaps builtins.__import__ would make the
// swapped callable compare equal to "the default" and never get called.
//
// CPython: pycore_interp.h interp->imports.import_func (captured at init)
DefaultImport = importFn

// breakpoint() forwards to sys.breakpointhook. Register the builtin
// here and hand the default hook to sys so sys.breakpointhook and
Expand Down
Loading