Repository navigation
v2.12.0: faster createInstanceFromMap (direct constructor invokers, opt-in resolved maps) - #57
Merged
Merged
Conversation
…in resolved maps (v2.12.0) - Generator: each ConstructorReflection gets an `invoker` that calls the constructor directly; `invokeWith` falls back to Function.apply for code generated by earlier versions. - createInstanceFromMap: `valuesResolved` and `exactKeys` options for maps whose keys and values are already resolved (e.g. bones_api DB rows); the fast path builds invoker arguments straight from the map and falls back to the MethodInvocation path on any error. - Field-setting fallback: set fields through the cached no-object FieldReflections (setFor/setNullableFor), no per-row allocation. - benchmark/create_instance_from_map.dart (+ README), fixture and tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benchmark/HISTORY.md, following bones_api's benchmark/db_users/HISTORY.md: progression table, the --markdown entries of both versions (alternated runs), and the bones_api db_users end-to-end comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lls (review of #57) - The generated invoker writes each omitted parameter's default in its own code, in the parameter's type context: an omitted argument arrives as `ConstructorReflection.absentArgument`. Passing the reflected `ParameterReflection.defaultValue` broke `double x = 0` (an `int`) and `List<String> l = const []` (a `List<dynamic>`) with a TypeError. - Invoker arguments are cast to their parameter types (`strict-casts` clean); constructors with parameters typed by a type parameter get no invoker (Function.apply). - A direct call that throws is reported through the MethodInvocation path's retry/report logic without calling the constructor again. - The direct call applies whenever there is no `fieldValueResolver`, so `valuesResolved` is only a `createInstanceFromMap` option (no longer threaded through 4 more signatures). - One "omittable parameter" rule shared by `methodInvocation` and the direct call. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esolved Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ugs they found Tests: - Defaults whose reflected value loses its type (Set, Map<String, List<int>>, Duration, enum, a static const by its bare name, a nullable parameter with a non-null default); fromJson with typed defaults; parameters typed by a type parameter (no invoker); the `valuesResolved` contract (no conversion); a throwing / case-insensitive fieldNameResolver on the direct path; MethodInvocations that don't fit the constructor. - generated_code_test: a package analyzed with strict-casts and avoid_dynamic_calls (--fatal-infos), with defaults from a prefixed import, a static const and an inherited super-parameter, and a program that runs the invokers (direct and JSON paths) and checks the runtime types. Generator fixes (generated code that didn't compile): - A default value referencing a static member by its bare name is qualified by its class. - A function-typed field using a class type parameter is reflected as `dynamic`. - supperTypes from a prefixed import keep the prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ter compiles Reflected as `dynamic` (not `Function`): a `Function?` setter value can't be assigned to a `T Function()?` field (no implicit downcast). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #57 +/- ##
==========================================
+ Coverage 92.56% 93.53% +0.97%
==========================================
Files 14 14
Lines 6428 6532 +104
==========================================
+ Hits 5950 6110 +160
+ Misses 478 422 -56
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…h the raw class
`T Function()? make` was reflected as `dynamic`; it's now the type the
generated code reads it as, through the raw class (type parameters as their
bounds): `dynamic Function()?`, or `Base Function(int, {String? label})?` for
`T extends Base`. Built from its parts (qualified types, nullability of the
function type, its parameters and return type kept); generic function types
stay `dynamic`. Its generated setter compiles under strict-casts.
e2e: settable and final function fields, a prefixed bound, positional /
optional / named / required parameters, and every nullability shape, checked
in the generated code and at runtime.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nherited function-typed fields - Dartdocs with examples for _Field.typeNameAsNullableCode / _typeThroughRawReceiver and the DartType helpers typeNameAsCode, typeNameAsNullableCode, functionTypeAsCode, hasTypeParameter, isResolvableType, isParameterType, isNullable. - A subclass with the type argument (`extends Box<int>`): its invoker, and its inherited `T Function()?` field reflected as `int Function()?`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `V` of `FieldReflection<O, V>` for any field whose type uses a class type parameter (`T`, `List<T>`, `Map<String, T>`, function types) is the type the generated code reads it as through the raw class: type parameters as their bounds, or as the type arguments of a subclass or mixin application (`IntBox extends Box<int>`: `int?`, `List<int>`). Their setters receive the type they assign and compile under strict-casts (they didn't for a bounded `T`). `TypeReflection`s (JSON decoding) are unchanged. - `qualifiedTypeCode`: types written with qualified type arguments and function types, keeping non-generic typedef names. - Generated: TestOpB.value `dynamic` -> `double?`, TestIntBox.items `List<dynamic>` -> `List<int>`. - e2e (strict-casts): settable bounded/unbounded `T?`, `List<T>`, `Map<String, T Function()?>`, a typedef parameter, and an `IntBox` subclass, checked in code and at runtime; unit tests for the inherited and mixin field types. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t independent of checkout file times - test_chrome: an `int` for a `double` fits on the web (both are JS numbers), so the `valuesResolved` contract test now uses values that don't fit on any platform (a `String` for a `double`, a `List<dynamic>` for a `List<String>`), each checked on its own. - test_vm (flaky): the inspector test compared the checked-in sources' and generated files' times, which in a fresh checkout only reflect the order `git` wrote them in. It now gives the generated files the current time (checking them by version), and a new test covers expiry by time and by version with files whose times are set. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…invokers - Many parameters of the same type (named, not in alphabetical order): each value in its position on every path. - Generic classes with type arguments (as entity references), lists of entities, maps, a typedef, nullable elements, enums, `Object?`, `dynamic` and records; type arguments that don't fit fail as with `Function.apply`. - Defaults: static method and top-level tear-offs, negatives, escapes, const collections and instances. - `const`, `factory`, redirecting and redirecting `factory` constructors. - Constructor choice: the first constructor that fits a value (with usage sorting), a non-mandatory `@JsonConstructor`, JSON name aliases. - `create()` + setters: final fields, nullables set to `null`, lists. - `valuesResolved` with a `fieldNameResolver` (snake case), alternating key shapes and modes, the `ReflectionFactory` registry, and parameter types vs field types. - e2e (strict-casts): a generic class from a prefixed import, records, and function defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This makes
ClassReflection.createInstanceFromMapfaster. bones_api calls it for every DB row, and its profile puts it at ~23% of SQLiteuser/listCPU.invokerper constructor. It calls the constructor withoutFunction.apply:strict-casts.ConstructorReflection.absentArgument. The invoker then passes the default written in its own code, in the parameter's type context, sodouble x = 0gets0.0andconst []becomes aList<String>.T,List<T>) gets no invoker and keeps usingFunction.apply.Function.apply.createInstanceWithConstructorcalls constructors throughinvokeWith(MethodInvocation). When there is nofieldValueResolver, it builds the invoker arguments straight from the map. That covers constructors without optional positional parameters, in classes without JSON name aliases.methodInvocation.createInstanceFromMap(valuesResolved:, exactKeys:): options for maps whose values are already resolved (fieldValueResolveris ignored) and whose keys are the exact names (no matching on_prefix or case).create()+ setters): it no longer allocates aFieldReflectionper field and row..g.dartfiles are regenerated.Review fixes (ef2fef1)
ParameterReflection.defaultValuefor omitted parameters. That value has the wrong type fordouble x = 0(anint) and for untypedconst [], so calls threw aTypeError. Defaults are now written into the generated code.strict-casts).valuesResolvedis no longer passed through 4 more public signatures.Benchmarks
benchmark/create_instance_from_map.dart(--markdownentries inbenchmark/HISTORY.md). Apple M5, JIT, alternated with 2.11.0:bones_api
db_userson SQLite, ops/sec, alternated, with the reflection regenerated:instantiateFromMapImpluser/listuser/byStateuser/byIdNote for bones_api:
instantiateFromMapImplis also reached from JSON (e.g.storeAllFromJson), where values are not resolved (enum names,DateTimestrings, lists of maps). PassingvaluesResolved: trueon that path breaks entities with such fields. Pass the options only where values are already resolved (DB rows).Test plan
test/reflection_factory_instantiate_test.dartchecks thatFunction.apply, the invoker viaMethodInvocation, and the direct call give the same results or errors for:doubleparameters and typedconst []/const {}defaults;exactKeys.It also checks that no invoker is generated for type-parameter types and that a constructor which throws is called once per attempt.
invoker:code.dart analyzeanddart formatare clean, anddart testpasses all 538 tests. The generated code also analyzes clean understrict-casts.db_usersAPI tests pass on 2.12.0, both unchanged and with the options.ensure_build, which is expected because the reflection was regenerated.🤖 Generated with Claude Code