#7731·sway

Generic aggregate bound checking ignores trait type arguments

Author: DicksonWu654Created Sep 11, 2026Updated Sep 14, 2026

Generic aggregate bound checking ignores trait type arguments

Related Component

compiler

Problem

When checking whether an unknown generic satisfies the bounds of a nested aggregate, TypeId::check_type_parameter_bounds() compares only the trait call path and ignores the trait's type arguments. As a result, a function constrained by T: Mark<u8> can construct and return a type whose declaration requires T: Mark<u64>.

The equivalent direct concrete construction is correctly rejected, so this is a generic-boundary bypass rather than an accepted implementation of the required trait.

Steps

This script compiles and its test passes on v0.72.1:

script;

trait Mark<A> {}

struct NeedsU64<T> where T: Mark<u64> {
    value: T,
}

struct OnlyU8 { n: u64 }
impl Mark<u8> for OnlyU8 {}

fn forge<T>(x: T) -> NeedsU64<T> where T: Mark<u8> {
    NeedsU64 { value: x }
}

fn main() -> u64 {
    let impossible: NeedsU64<OnlyU8> = forge(OnlyU8 { n: 42 });
    impossible.value.n
}

#[test]
fn invalidly_bounded_type_is_inhabited() {
    assert(main() == 42);
}

Run:

forc test

Observed: 1 passed; 0 failed.

Control: replacing forge(...) with the direct construction below is rejected:

let impossible: NeedsU64<OnlyU8> = NeedsU64 {
    value: OnlyU8 { n: 42 },
};

The compiler then correctly reports that Mark<u64> is not implemented for OnlyU8.

In sway-core/src/type_system/id.rs, the TypeInfo::UnknownGeneric branch of check_type_parameter_bounds() collects only trait_constraint.trait_name, then uses name-only containment. The concrete branch below it already compares canonical names, arity, and resolved trait arguments.

Expected behavior

forge must be rejected because Mark<u8> does not satisfy Mark<u64>. Trait constraints with the same path but different type arguments are distinct requirements.

Possible Solution(s)

Compare complete substituted trait constraints in the unknown-generic branch: canonical path, arity, and recursively unified/resolved generic arguments. The concrete branch's constraint comparison may be reusable.

Add negative tests for identical trait paths with different type arguments, including nested aggregate return types and constructors.

This is not the same defect as closed #6374, which involved calling a method whose additional method-level constraint was not respected. Here the aggregate's declared bound is checked, but its trait arguments are discarded.

Installed components

forc 0.72.1
sway 0.72.1
commit dad95cc42b0383b4e3bacdeda9766565aa584a92