modular / modular/modular

[BUG] Unable to implement one trait with default functions from another trait

Open
#6,312 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

auto_priority_triaged bug mojo
Dominant language
Mojo
Stars
29.8k
Forks
3.2k
PR merge metrics
No merged PRs in 30d

Description

Bug description
Actual behavior

Compiler error: error: attempt to resolve a recursive reference to declaration 'MojoDefaultGPA.allocate'

Expected behavior

Program prints 523776.0

Steps to reproduce

Sorry, more minimal reproducers didn't seem to work.

from std.memory import alloc, UnsafePointer
from std.sys.info import CompilationTarget, size_of
from std.ffi import external_call


@fieldwise_init
struct MemoryAllocationError(Defaultable, ImplicitlyCopyable, Movable):
    """A generic "something went wrong" error type for allocators. It
    **SHOULD** generally be treated as equivalent to POSIX's ENOMEM.

    Allocators **SHOULD** provide more specific error types if they can, but
     **SHOULD** provide an implicit conversion to MemoryAllocationError if
    they do to aid in ecosystem compatibility.
    """

    pass

trait GeneralPurposeAllocator:
    """The base trait for allocator-like things.

    An allocator is used to manage memory, typically from the heap but not always, and provide users with a structured view of it. Users should use implementations to obtain pointers for use in data structures or higher-level abstractions over memory, and use deallocators to free memory when it is no longer needed. Users should carefully read the documentation of the allocator and the implemented traits, as this interfaces is **UNSAFE** and has requirements which the Mojo compiler cannot enforce that users must uphold. It is also encouraged to investigate `std.collections` for higher level data structures which may be more appropriate for users who do not need the full flexibility of this interface.

    Implementations **MUST** document what deallocators may be used to free memory return from their `allocate` method, provide other information about how memory is freed (ex: garbage collection), or state that all memory must be leaked. Users **MUST** ensure that they only use compatible allocators and deallocators together, and failure to do so is considered undefined behavior for the purposes of this interface. In the absence of documentation about compatible allocators, users **MUST** assume that all memory must be leaked. Types which implement both `GeneralPurposeAllocator` and `GeneralPurposeDeallocator` are not an exception to this documentation requirement.
    """
    comptime AllocationErrorType: AnyType
    comptime AllocateSingleRequiresMut: Bool

    fn allocate[
        mut: Bool,
        //,
        origin: Origin[mut = (True if Self.AllocateSingleRequiresMut else mut)],
        ElementType: AnyType,
        count: Int,
    ](ref[origin] self) raises (Self.AllocationErrorType) -> UnsafePointer[
        ElementType, MutExternalOrigin
    ]:
        """Used to allocate memory for `count` elements of type `ElementType`.

        WARNING: The alignment of the returned pointer is not specified by this trait. Implementations **SHOULD** document the alignment guarantees they provide, and users **MUST** ensure that they only use implementations whose alignment guarantees are sufficient for their use case. In the absence of documentation about alignment guarantees, users **MUST** assume that the returned pointer has an alignment equal to the platform's minimum access alignment (ex: 1 byte on x86_64). Software emulation for unaligned accesses which is transparent to programs (such as the "trap and emulate" strategy) is considered to decrease the minimum access alignment.

        Implementations **SHOULD** define `allocate` such that it produces a pointer to a unique location in memory which is not presently "allocated". The precise meaning of "allocated" is implementation defined, but generally refers to memory that has been produced by an allocator not passed to a deallocator. Implementations **MAY** use `comptime assert` or other mechanisms to restrict `count` to supported values. Implementations **SHOULD** ensure that `count` is non-negative and non-zero, using mechanisms such as `comptime assert` to provide feedback to the user if the implementation does not have a well defined behavior for such cases.
        """
        ...


trait RuntimeSizedGPA(GeneralPurposeAllocator):
    fn allocate_runtime_count[
        mut: Bool,
        //,
        origin: Origin[mut = (True if Self.AllocateSingleRequiresMut else mut)],
        ElementType: AnyType,
    ](ref[origin] self, count: Int) raises (
        Self.AllocationErrorType
    ) -> UnsafePointer[ElementType, MutExternalOrigin]:
        """Used to allocate memory for `count` elements of type `ElementType`.

        WARNING: The alignment of the returned pointer is not specified by this trait. Implementations **SHOULD** document the alignment guarantees they provide, and users **MUST** ensure that they only use implementations whose alignment guarantees are sufficient for their use case. In the absence of documentation about alignment guarantees, users **MUST** assume that the returned pointer has an alignment equal to the platform's minimum access alignment (ex: 1 byte on x86_64). Software emulation for unaligned accesses which is transparent to programs (such as the "trap and emulate" strategy) is considered to decrease the minimum access alignment.

        Implementations **SHOULD** define `allocate` such that it produces a pointer to a unique location in memory which is not presently "allocated". The precise meaning of "allocated" is implementation defined, but generally refers to memory that has been produced by an allocator not passed to a deallocator. Implementations **SHOULD** ensure that `count` is non-negative and non-zero, avoiding undefined behavior if the implementation does not have a well defined behavior for such cases.
        """
        ...

    fn allocate[
        mut: Bool,
        //,
        origin: Origin[mut = (True if Self.AllocateSingleRequiresMut else mut)],
        ElementType: AnyType,
        count: Int,
    ](ref[origin] self) raises (Self.AllocationErrorType) -> UnsafePointer[
        ElementType, MutExternalOrigin
    ]:
        return self.allocate_runtime_count[origin, ElementType](count)


@fieldwise_init
struct MojoDefaultGPA(
    Defaultable,
    RuntimeSizedGPA,
    ImplicitlyCopyable,
    Movable,
):
    comptime AllocationErrorType: AnyType = MemoryAllocationError
    comptime AllocateSingleRequiresMut: Bool = False

    fn allocate_runtime_count[
        mut: Bool,
        //,
        origin: Origin[mut = (True if Self.AllocateSingleRequiresMut else mut)],
        ElementType: AnyType,
    ](ref[origin] self, count: Int) raises (
        Self.AllocationErrorType
    ) -> UnsafePointer[ElementType, MutExternalOrigin]:
        return alloc[ElementType](count)


def sum(buffer: UnsafePointer[Float32, MutExternalOrigin]) -> Float32:
    var result: Float32 = 0.0
    for i in range(1024):
        result += buffer[i]
    return result

def main():
    var alloc = MojoDefaultGPA()
    var ptr = alloc.allocate[Float32](1024)
    
    for i in range(1024):
        ptr[i] = Float32(i)

    print(sum(ptr))

Interestingly, the following DOES NOT reproduce the bug, meaning it's likely going to involve something else I did:

trait T1:
    def foo(self) -> Int32:
        ...

trait T2(T1):
    def bar(self) -> Int32:
        ...

    def foo(self) -> Int32:
        return self.bar()

trait T3(T2):
    def baz(self) -> Int32:
        ...

    def bar(self) -> Int32:
        return self.baz()

@fieldwise_init
struct Foo(T3):
    def baz(self) -> Int32:
        return 42

def main():
    var foo = Foo()
    print(foo.foo())
System information
System
------------
       Pixi version: 0.58.0
           Platform: linux-64
   Virtual packages: __unix=0=0
                   : __linux=6.18.16=0
                   : __glibc=2.42=0
                   : __archspec=1=zen4
          Cache dir: /home/ohilyard/.cache/rattler/cache
       Auth storage: /home/ohilyard/.rattler/credentials.json
   Config locations: No config files found

Global
------------
            Bin dir: /home/ohilyard/.pixi/bin
    Environment dir: /home/ohilyard/.pixi/envs
       Manifest dir: /home/ohilyard/.pixi/manifests/pixi-global.toml

Workspace
------------
               Name: Mojo
      Manifest file: /home/ohilyard/Documents/projects/mojo/stdlib/mojo/mojo/pixi.toml
       Last updated: 29-03-2026 21:17:49

Environments
------------
        Environment: default
           Features: default
           Channels: conda-forge, https://conda.modular.com/max-nightly
   Dependency count: 1
       Dependencies: mojo
   Target platforms: linux-64, linux-aarch64, osx-arm64
    Prefix location: /home/ohilyard/Documents/projects/mojo/stdlib/mojo/mojo/.pixi/envs/default
              Tasks: format, tests

Mojo 0.26.2.0.dev2026021805 (5733ca77)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by running the supplied Mojo reproducer and confirm the recursive-reference compiler error, then compare it with the smaller T1/T2/T3 example that succeeds. Trace the trait default-function resolution involved in MojoDefaultGPA and verify the fix by producing the expected output, 523776.0, without regressing the working example.

Written by the indexing model from the issue text.

Assessment

Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.