DapperLib / DapperAOT

Build time tools in the flavor of Dapper
Other
357 stars 19 forks source link

Non-trivial construction #28

Closed mgravell closed 1 year ago

mgravell commented 1 year ago

Right now, the materializer is akin to:

TheType obj = new();
while (...)
{
    switch (...)
    {
        case 4223423:
            obj.Foo = /* read the value */
            break;
    }
}
return obj;

This only works for trivial construction; it does not support more complex scenarios:

  1. custom constructors
  2. init-only properties
  3. read-only fields (deferred for now, as this also requires materializer colocation)

A custom constructor (at most one per type) is identified by:

If a custom constructor is selected, the parameters are checked against the member list using normalized / insensitive rules; if any paired members do not match field type (question: nullability?), a generator error is emitted and the constructor is deselected


Init-only properties are any non-static properties that have the init modifier, for example public string Name { get; init; }


In any of these scenarios, we must collect the values into locals, and defer construction; I would propose simply value0, value1, etc where the index is the position of the discovered member; each should be assigned default, awaiting values from the database; we then collect fields into these variables instead of the object:

int value0 = default;
string? value1 = default;
DateTime value2 = default;

while (...)
{
    switch (...)
    {
        case 4223423:
            value1 = /* read the value */
            break;
    }
}
// construction not shown yet

The construction step depends on whether a custom constructor is selected, and whether the parameters for such custom constructor covers all members; there are 3 possible outcomes:

  1. custom constructor covers all members
return new TheType(value0, value2, value1);

(noting that the parameter order does not necessarily match our declaration order, so it is not necessarily strict order)

  1. custom constructor covers some but not all members (which may or may not include init-only properties)
return new TheType(value0, value2)
{
    Foo = value1,
};
  1. no custom constructor (and which by elimination: must include at least one init-only property)
return new TheType
{
    Foo = value1,
};

Implementation notes:

DRY:

†: the 4th quadrant in that with/without matrix is: simple construction, so: already handled - i.e. without constructor, everything is additional members, none of which are init-only - the difference being that in that simple scenario, we're not using the stack to gather the values - we're pushing them directly onto the object