Skip to content

Raise a proper TypeError when a property assignment cannot be performed#141

Open
jhonabreul wants to merge 3 commits into
QuantConnect:masterfrom
jhonabreul:bug-property-set-silent-conversion-failure
Open

Raise a proper TypeError when a property assignment cannot be performed#141
jhonabreul wants to merge 3 commits into
QuantConnect:masterfrom
jhonabreul:bug-property-set-silent-conversion-failure

Conversation

@jhonabreul

@jhonabreul jhonabreul commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

What does this implement/fix?

Fixes two related property-assignment bugs that surfaced together when a Lean Python algorithm assigned a pure-Python object to self.universe_manager (which snake-cases onto QCAlgorithm.UniverseManager { get; private set; }) and got the opaque SystemError: error return without exception set instead of an actionable error:

  1. Converter.ToManagedValue could fail without setting a Python error even when called with setError: true. Three exits returned false silently: the final fall-through (plain Python object with no conversion to an arbitrary CLR class), the reflected-class-object case (converting a CLR class object to something other than Type), and non-MethodBinding managed types (method/module objects). PropertyObject.tp_descr_set trusts setError: true and returns -1, so CPython's release-build eval loop raises SystemError: error return without exception set at the assignment. These exits now set the same '{type}' value cannot be converted to {obType} TypeError the type_error label already uses, preserving any more specific error a probing sub-path (e.g. a throwing implicit operator) left pending.

  2. Properties with private setters were silently writable from Python. PropertyObject.CacheAccessors resolved setters with GetSetMethod(nonPublic: true), so { get; private set; } properties bypassed the "property is read-only" guard and wrote engine-internal state through reflection (convertible values succeeded silently — the Lean repro replaced the algorithm's UniverseManager and hung the engine). The setter is now discarded when it does not satisfy ClassManager.ShouldBindMethod (the repo's accessibility policy: public, protected, protected internal), so private/internal setters read as read-only while protected setters keep working.

With both fixes, the Lean repro turns from SystemError: error return without exception set into TypeError: property is read-only, and non-convertible assignments to writable properties report TypeError: '<class>' value cannot be converted to <type>.

Each fix is a separate commit carrying its own regression tests; the tests were verified red before each fix and green after.

Does this close any currently open issues?

No open issue; root-caused from a Lean user report of error return without exception set during algorithm initialization.

Any other comments?

Behavioral note: code that (likely unknowingly) relied on assigning to private-setter C# properties from Python will now get TypeError: property is read-only. This aligns property writes with ClassManager.ShouldBindMethod, which already governs method/field exposure.

End-to-end verification against Lean master (the original repro): self.universe_manager = <pure python object> in a Python algorithm now fails with TypeError: property is read-only at the offending line instead of SystemError: error return without exception set, and a convertible value no longer silently replaces the algorithm's UniverseManager (which previously hung the engine on shutdown). Full embed suite on the combined branch: 974 passed, 0 failed, 8 pre-existing environment skips.

@jhonabreul jhonabreul force-pushed the bug-property-set-silent-conversion-failure branch from d8e51db to f039e68 Compare July 15, 2026 21:59
Converter.ToManagedValue had three exits that reported failure without
setting a Python error: the final fall-through when a plain Python object
has no conversion to the target CLR class, the ClassBase exit when a
reflected class object is converted to something other than Type, and the
exit for managed values that are neither CLRObject, ClassBase, nor
MethodBinding (e.g. a MethodObject). Callers that pass setError true, such
as PropertyObject.tp_descr_set, trust the failure to carry a pending error
and return -1, so CPython raised "SystemError: error return without
exception set" instead of a useful message. Assigning a pure Python object
to a C# property of a CLR class type reproduced this. These exits now set
the conventional TypeError ("'<type>' value cannot be converted to
<target>") when setError is true and no more specific error is already
pending from a probing sub-path; setError false behavior is unchanged.
Python assignment to a CLR property resolved the setter with nonPublic: true,
so a property declared { get; private set; } was silently writable from
Python: the read-only guard in tp_descr_set never fired and reflection
invoked the private setter, mutating state the class never exposed.

Align property setter binding with the accessibility policy in
ClassManager.ShouldBindMethod (public, protected, protected internal):
CacheAccessors now discards a resolved setter that fails that check, so
assignment raises TypeError 'property is read-only'. Protected setters
remain writable from Python subclasses.
@jhonabreul jhonabreul force-pushed the bug-property-set-silent-conversion-failure branch from f039e68 to f069ab4 Compare July 15, 2026 22:44
Bump package <Version>, AssemblyVersion/AssemblyFileVersion and the
perf-test baseline reference to 2.0.64.
@jhonabreul jhonabreul marked this pull request as ready for review July 15, 2026 22:51
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.

1 participant