Skip to content

Remove the TraitListObject, TraitDictObject and TraitSetObject cases from default value inference - #1902

Open
Kayvan-Zahiri wants to merge 2 commits into
enthought:mainfrom
Kayvan-Zahiri:fix-trait-container-object-default
Open

Kayvan-Zahiri wants to merge 2 commits into
enthought:mainfrom
Kayvan-Zahiri:fix-trait-container-object-default

Conversation

@Kayvan-Zahiri

@Kayvan-Zahiri Kayvan-Zahiri commented Sep 23, 2026 •

Copy link
Copy Markdown

Fixes #1591.

Following @mdickinson's review, this removes the three branches in _infer_default_value_type that mapped TraitListObject, TraitDictObject and TraitSetObject to the trait_*_object default value types. Those types only work with a List, Dict or Set handler, and List, Dict and Set set their default value type explicitly, so the branches only ever applied to other traits, where reading the attribute failed. These objects now get what plain containers get from the same inference: list_copy, dict_copy, and constant for sets. The earlier Trait()-specific change is reverted.

Behavior note: lists and dicts are now copied per instance. Sets are still shared, since there is no set-copy default value type.

Tests: the issue's add_trait example, list, dict and set defaults through Trait(), and a plain TraitType subclass (the example from the review) all error on main and pass here. Full suite: 1633 tests OK, flake8 clean.

Written with AI assistance. The runs above are real.

@mdickinson

mdickinson commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

@Kayvan-Zahiri Thanks for the PR and the analysis.

Some project context: Trait itself is ancient and not really something we want to support; we've been edging (very) slowly towards being able to remove it altogether. Unfortunately, it's still tangled up with existing machinery (like add_trait), so we're not there yet. But the main takeaway is - we don't really want to add complication to the codebase to cater for something that's very much legacy code.

I think there's another potential way to solve this issue: instead of adding extra conditions, we remove the three branches here. Those branches are the cause of the 5.2.0 -> 6.0 regression noted in the issue; they're not really useful, and they're the cause of this issue as well as potentially other ones.

An example of a related bug that isn't touching the legacy Trait code, and that would also be fixed by deleting the three branches:

from traits.api import HasTraits, List, TraitType

class Source(HasTraits):
    values = List([1, 2])

class MyType(TraitType):
    pass

class A(HasTraits):
    value = MyType(Source().values)

A().value

The above currently raises TypeError, with roughly the same cause.

As suggested in review: instead of special-casing Trait(), remove the
three branches in _infer_default_value_type that picked the
trait_*_object default value types. Those types need a List, Dict or
Set handler, so any other trait given one of these objects as its
default failed on read. The objects now get the same default value
types as plain lists, dicts and sets. Reverts the traits.py change from
the previous commit and adds a test for a plain TraitType.
@Kayvan-Zahiri Kayvan-Zahiri changed the title Fix Trait() given a TraitListObject, TraitDictObject or TraitSetObject default Remove the TraitListObject, TraitDictObject and TraitSetObject cases from default value inference Sep 24, 2026
@Kayvan-Zahiri

Copy link
Copy Markdown
Author

Thanks, that's a cleaner fix. I've pushed it: the three branches are gone, the Trait() change is reverted, and your MyType example is now a regression test next to the four from before. All five error on main and pass with the change, and the full suite passes (1633 OK). I updated the description to match.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

minlen not present on TraitInstance

2 participants