diff --git a/CHANGELOG.md b/CHANGELOG.md index a68ecfc..bc5b5e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ Each release has a `## [version]` section. When a `v*.*.*` tag is pushed, the Publish workflow copies that section into the GitHub release, and it fails if the section is missing. +## [Unreleased] + +### Docs + +- Caveats document 15-argument `TypeLoadException`, C# optional parameters, and `tryGet` / `exists` on a null target (#110). + ## [6.0.0] ### Breaking diff --git a/README.md b/README.md index d110674..9be5953 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,9 @@ You can still `open Dynamitey` for `Build`, `Dynamic.Curry`, `DynamicObjects.Dic - The DLR cannot see **explicit interface members** (same as C# `dynamic`). - **Not trim-safe or NativeAOT-safe.** +- **15 or more arguments** throw `TypeLoadException` (ImpromptuInterface was never shipped). Fourteen work. +- **C# optional parameters** must all be passed. The DLR does not fill defaults. +- `tryGet` / `exists` on a **null target** throw `NullReferenceException` (not `None` / `false`). - Do not build member names from untrusted input. [SECURITY.md](SECURITY.md). - Historical: .NET Core 2.0.0–2.0.2 broke `dynamic` on nested types inside generics ([#11](https://github.com/fsprojects/FSharp.Interop.Dynamic/issues/11)). Current TFMs are fine. diff --git a/docfx/docs/caveats.md b/docfx/docs/caveats.md index ce20960..f0d28cc 100644 --- a/docfx/docs/caveats.md +++ b/docfx/docs/caveats.md @@ -18,6 +18,18 @@ Annotate `'T` / `unit` so the binder and the conversion agree. `Dyn.exists "x"` is true if `InvokeGet` succeeded, including when the value is null. `tryGet` returns `Some null` for a reference type. That is different from missing. +## Null target + +`Dyn.tryGet` and `Dyn.exists` on a **null target** throw `NullReferenceException`. That is not a binder miss, so it is not `None` / `false`. Same for `Dyn.set`. A present null *value* is still `Some null` / `exists` true. Changing the null-target contract is [#111](https://github.com/fsprojects/FSharp.Interop.Dynamic/issues/111). + +## More than 14 arguments + +A call with **15 or more** arguments throws `TypeLoadException: Cannot Emit long delegates without ImpromptuInterface installed`. Fourteen arguments work. Dynamitey 3.0.3 never shipped ImpromptuInterface with this package. A fix belongs in a newer Dynamitey, not in a vendor here ([#29](https://github.com/fsprojects/FSharp.Interop.Dynamic/issues/29)). + +## C# optional parameters + +The DLR does not fill CLR optional defaults. `obj?Opt(5)` and `namedArg "a" 5` both fail (`No overload takes 1 arguments`) when `Opt` is `Opt(int a, int b = 0)`. Pass every argument. Named args work when the full set is present. + ## Function-typed results delay the call If F# infers `'TResult` as a function, `?` / `Dyn.get` / `Dyn.invokeMember` return a callable. A missing member in that mode used to look like `Some (fun …)` until you applied it. `tryGet` does `InvokeGet` first, so a missing member is `None` even when `'T` is a function. diff --git a/docfx/docs/getting-started.md b/docfx/docs/getting-started.md index d29e278..12b4033 100644 --- a/docfx/docs/getting-started.md +++ b/docfx/docs/getting-started.md @@ -69,4 +69,4 @@ let _: unit = items?Add("x") - [Operators](operators.md) — `?`, `?<-`, `!?` in detail - [Dyn](dyn.md) — the functions behind those operators - [tryGet and exists](tryget.md) — the issue #27 surface -- [Caveats](caveats.md) — explicit interface members, AOT, conversion vs missing +- [Caveats](caveats.md) — explicit interface members, AOT, conversion vs missing, 15-arg, optionals, null target diff --git a/docfx/docs/tryget.md b/docfx/docs/tryget.md index 448fe02..9543fa6 100644 --- a/docfx/docs/tryget.md +++ b/docfx/docs/tryget.md @@ -27,6 +27,7 @@ Lookup is `Dynamic.InvokeGet` only. Then the existing conversion / callable wrap | Member present, value cannot convert to `'T` | throws (`InvalidCastException` or a binder exception) — **not** `None` | | Member missing, `'T` is a function type | `None` immediately (does not return a lazy callable) | | Getter throws something else | that exception propagates | +| Target is null | `NullReferenceException` (not `None`). [#110](https://github.com/fsprojects/FSharp.Interop.Dynamic/issues/110), [#111](https://github.com/fsprojects/FSharp.Interop.Dynamic/issues/111) | ## `Dyn.exists` @@ -35,7 +36,7 @@ o |> Dyn.exists "myProp" // true o |> Dyn.exists "nope" // false ``` -True when `InvokeGet` succeeds. A present null is still present. A throwing getter is not reported as missing. +True when `InvokeGet` succeeds. A present null is still present. A throwing getter is not reported as missing. A **null target** throws `NullReferenceException`, not `false`. `exists` does not convert the value. That is the difference from `tryGet >> Option.isSome` when conversion would fail: `exists` can be true while `tryGet` as `int` throws.