@fable-org/fable-library-js 2.4.1 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Async.js +34 -23
- package/AsyncBuilder.js +23 -2
- package/BitConverter.js +2 -2
- package/CHANGELOG.md +20 -1
- package/Date.js +7 -14
- package/DateOffset.js +8 -17
- package/Directory.js +11 -0
- package/Long.js +18 -27
- package/package.json +1 -1
- package/quotation.js +25 -8
package/Async.js
CHANGED
|
@@ -7,10 +7,6 @@ import { Exception } from "./Util.js";
|
|
|
7
7
|
function emptyContinuation(_x) {
|
|
8
8
|
// NOP
|
|
9
9
|
}
|
|
10
|
-
// see AsyncBuilder.Delay
|
|
11
|
-
function delay(generator) {
|
|
12
|
-
return protectedCont((ctx) => generator()(ctx));
|
|
13
|
-
}
|
|
14
10
|
// MakeAsync: body:(AsyncActivation<'T> -> AsyncReturn) -> Async<'T>
|
|
15
11
|
export function makeAsync(body) {
|
|
16
12
|
return body;
|
|
@@ -49,18 +45,27 @@ export function throwIfCancellationRequested(token) {
|
|
|
49
45
|
}
|
|
50
46
|
}
|
|
51
47
|
export function startChild(computation, ms) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
promiseToRun =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
return protectedCont((ctx) => {
|
|
49
|
+
// Share the parent's cancellation token so cancelling the parent
|
|
50
|
+
// cancels the child, like .NET Async.StartChild
|
|
51
|
+
const promise = startAsPromise(computation, ctx.cancelToken);
|
|
52
|
+
let promiseToRun = promise;
|
|
53
|
+
if (ms) {
|
|
54
|
+
// Race the computation against a timeout: whichever settles first wins.
|
|
55
|
+
promiseToRun = new Promise((resolve, reject) => {
|
|
56
|
+
const timeoutId = setTimeout(() => reject(TimeoutException_$ctor()), ms);
|
|
57
|
+
promise.then(value => { clearTimeout(timeoutId); resolve(value); }, error => { clearTimeout(timeoutId); reject(error); });
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Prevent an unhandled rejection when the child is cancelled or fails
|
|
61
|
+
// while nobody awaits it (in F# an unobserved child's cancellation or
|
|
62
|
+
// failure is simply not propagated). awaitPromise attaches its own
|
|
63
|
+
// handlers to the same promise, so awaiting still observes the result.
|
|
64
|
+
promiseToRun.catch(emptyContinuation);
|
|
65
|
+
// JS Promises are hot, computation has already started
|
|
66
|
+
// but we delay returning the result
|
|
67
|
+
protectedReturn(awaitPromise(promiseToRun))(ctx);
|
|
68
|
+
});
|
|
64
69
|
}
|
|
65
70
|
export function awaitPromise(p) {
|
|
66
71
|
return fromContinuations((conts) => p.then(conts[0]).catch((err) => (err instanceof OperationCanceledException
|
|
@@ -106,17 +111,19 @@ export function ignore(computation) {
|
|
|
106
111
|
return protectedBind(computation, (_x) => protectedReturn(void 0));
|
|
107
112
|
}
|
|
108
113
|
export function parallel(computations) {
|
|
109
|
-
|
|
114
|
+
// Children share the parent's cancellation token, like .NET Async.Parallel
|
|
115
|
+
return protectedCont((ctx) => awaitPromise(Promise.all(Array.from(computations, (w) => startAsPromise(w, ctx.cancelToken))))(ctx));
|
|
110
116
|
}
|
|
111
117
|
export function sequential(computations) {
|
|
112
|
-
function _sequential(computations) {
|
|
118
|
+
function _sequential(computations, cancelToken) {
|
|
113
119
|
let pr = Promise.resolve([]);
|
|
114
120
|
for (const c of computations) {
|
|
115
|
-
pr = pr.then(results => startAsPromise(c).then(r => results.concat([r])));
|
|
121
|
+
pr = pr.then(results => startAsPromise(c, cancelToken).then(r => results.concat([r])));
|
|
116
122
|
}
|
|
117
123
|
return pr;
|
|
118
124
|
}
|
|
119
|
-
|
|
125
|
+
// Children share the parent's cancellation token, like .NET Async.Sequential
|
|
126
|
+
return protectedCont((ctx) => awaitPromise(_sequential(computations, ctx.cancelToken))(ctx));
|
|
120
127
|
}
|
|
121
128
|
export function sleep(millisecondsDueTime) {
|
|
122
129
|
return protectedCont((ctx) => {
|
|
@@ -139,10 +146,14 @@ export function startImmediate(computation, cancellationToken) {
|
|
|
139
146
|
}
|
|
140
147
|
export function startWithContinuations(computation, continuation, exceptionContinuation, cancellationContinuation, cancelToken) {
|
|
141
148
|
const trampoline = new Trampoline();
|
|
149
|
+
// Mark the computation completed as soon as a terminal continuation is entered
|
|
150
|
+
// so protectedCont lets exceptions from continuation code propagate instead of
|
|
151
|
+
// routing them to onError (see Trampoline.completed).
|
|
152
|
+
const done = (cont) => (x) => { trampoline.completed = true; return cont(x); };
|
|
142
153
|
computation({
|
|
143
|
-
onSuccess: continuation ? continuation : emptyContinuation,
|
|
144
|
-
onError: exceptionContinuation,
|
|
145
|
-
onCancel: cancellationContinuation,
|
|
154
|
+
onSuccess: done(continuation ? continuation : emptyContinuation),
|
|
155
|
+
onError: done(exceptionContinuation),
|
|
156
|
+
onCancel: done(cancellationContinuation),
|
|
146
157
|
cancelToken: cancelToken ? cancelToken : defaultCancellationToken,
|
|
147
158
|
trampoline,
|
|
148
159
|
});
|
package/AsyncBuilder.js
CHANGED
|
@@ -47,6 +47,7 @@ export class Trampoline {
|
|
|
47
47
|
}
|
|
48
48
|
constructor() {
|
|
49
49
|
this.callCount = 0;
|
|
50
|
+
this.completed = false;
|
|
50
51
|
}
|
|
51
52
|
incrementAndCheck() {
|
|
52
53
|
return this.callCount++ > Trampoline.maxTrampolineCallCount;
|
|
@@ -67,6 +68,9 @@ export function protectedCont(f) {
|
|
|
67
68
|
f(ctx);
|
|
68
69
|
}
|
|
69
70
|
catch (err) {
|
|
71
|
+
if (ctx.trampoline.completed) {
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
70
74
|
ctx.onError(ensureErrorOrException(err));
|
|
71
75
|
}
|
|
72
76
|
});
|
|
@@ -76,6 +80,13 @@ export function protectedCont(f) {
|
|
|
76
80
|
f(ctx);
|
|
77
81
|
}
|
|
78
82
|
catch (err) {
|
|
83
|
+
// Once a terminal continuation has run the computation is complete, so
|
|
84
|
+
// an exception from user continuation code must propagate rather than be
|
|
85
|
+
// routed to onError (which would resolve the computation a second time,
|
|
86
|
+
// e.g. a succeeded try/with body re-entering its with handler).
|
|
87
|
+
if (ctx.trampoline.completed) {
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
79
90
|
ctx.onError(ensureErrorOrException(err));
|
|
80
91
|
}
|
|
81
92
|
}
|
|
@@ -85,12 +96,18 @@ export function protectedBind(computation, binder) {
|
|
|
85
96
|
return protectedCont((ctx) => {
|
|
86
97
|
computation({
|
|
87
98
|
onSuccess: (x) => {
|
|
99
|
+
// Only guard the binder evaluation itself: the resulting computation
|
|
100
|
+
// is protected on its own, and re-catching what it throws would
|
|
101
|
+
// route continuation exceptions back into onError (double-resolve).
|
|
102
|
+
let bound;
|
|
88
103
|
try {
|
|
89
|
-
binder(x)
|
|
104
|
+
bound = binder(x);
|
|
90
105
|
}
|
|
91
106
|
catch (err) {
|
|
92
107
|
ctx.onError(ensureErrorOrException(err));
|
|
108
|
+
return;
|
|
93
109
|
}
|
|
110
|
+
bound(ctx);
|
|
94
111
|
},
|
|
95
112
|
onError: ctx.onError,
|
|
96
113
|
onCancel: ctx.onCancel,
|
|
@@ -155,12 +172,16 @@ export class AsyncBuilder {
|
|
|
155
172
|
cancelToken: ctx.cancelToken,
|
|
156
173
|
trampoline: ctx.trampoline,
|
|
157
174
|
onError: (ex) => {
|
|
175
|
+
// See protectedBind: only guard the handler evaluation itself.
|
|
176
|
+
let handled;
|
|
158
177
|
try {
|
|
159
|
-
catchHandler(ex)
|
|
178
|
+
handled = catchHandler(ex);
|
|
160
179
|
}
|
|
161
180
|
catch (err) {
|
|
162
181
|
ctx.onError(ensureErrorOrException(err));
|
|
182
|
+
return;
|
|
163
183
|
}
|
|
184
|
+
handled(ctx);
|
|
164
185
|
},
|
|
165
186
|
});
|
|
166
187
|
});
|
package/BitConverter.js
CHANGED
|
@@ -77,7 +77,7 @@ export function doubleToInt64Bits(value) {
|
|
|
77
77
|
export function toBoolean(bytes, offset) {
|
|
78
78
|
const array = ArrayBuffer.isView(bytes) ? bytes : Uint8Array.from(bytes);
|
|
79
79
|
const view = new DataView(array.buffer, array.byteOffset, array.byteLength);
|
|
80
|
-
return view.getUint8(offset)
|
|
80
|
+
return view.getUint8(offset) !== 0;
|
|
81
81
|
}
|
|
82
82
|
export function toChar(bytes, offset) {
|
|
83
83
|
const array = ArrayBuffer.isView(bytes) ? bytes : Uint8Array.from(bytes);
|
|
@@ -134,5 +134,5 @@ export function toString(bytes, offset, count) {
|
|
|
134
134
|
else if (offset != null) {
|
|
135
135
|
buffer = buffer.subarray(offset);
|
|
136
136
|
}
|
|
137
|
-
return Array.from(buffer).map((b) => ("0" + b.toString(16)).slice(-2)).join("-");
|
|
137
|
+
return Array.from(buffer).map((b) => ("0" + b.toString(16)).slice(-2).toUpperCase()).join("-");
|
|
138
138
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
last_commit_released:
|
|
2
|
+
last_commit_released: 6568f35ece227fc30ce84111c8d2e975b09bac00
|
|
3
3
|
updaters:
|
|
4
4
|
- package.json:
|
|
5
5
|
file: package.json
|
|
@@ -15,6 +15,25 @@ All notable changes to this project will be documented in this file.
|
|
|
15
15
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
16
16
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
17
17
|
|
|
18
|
+
## 2.5.0 - 2026-07-15
|
|
19
|
+
|
|
20
|
+
### 🚀 Features
|
|
21
|
+
|
|
22
|
+
* *(js/ts)* Add `System.IO.Directory` support (Exists, CreateDirectory) (#4798) ([6568f35e](https://github.com/fable-compiler/Fable/commit/6568f35ece227fc30ce84111c8d2e975b09bac00))
|
|
23
|
+
* *(js/ts/python/beam)* Preserve member calls and pattern matches in quotations (#4780) ([55482e6b](https://github.com/fable-compiler/Fable/commit/55482e6b5e6306fd4dbaba5759024313cb26682a))
|
|
24
|
+
|
|
25
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/a8d9bf5f0b7cc4560bb107963348e765695c22b6..6568f35ece227fc30ce84111c8d2e975b09bac00)</small></strong>
|
|
26
|
+
|
|
27
|
+
## 2.4.2 - 2026-07-11
|
|
28
|
+
|
|
29
|
+
### 🐞 Bug Fixes
|
|
30
|
+
|
|
31
|
+
* *(js/ts)* Async cancellation propagation and continuation double-resolve (#4746) ([c78a9a8e](https://github.com/fable-compiler/Fable/commit/c78a9a8ed20234feddf3375b61e91098f9261bee))
|
|
32
|
+
* *(js/ts)* Date arithmetic: PM parsing, AddMonths day clamp, AddYears offset (#4744) ([be0558eb](https://github.com/fable-compiler/Fable/commit/be0558ebe07f78f7a93ec0c34592d7c066b59fc7))
|
|
33
|
+
* *(js/ts)* Int64 non-decimal parsing and BitConverter .NET mismatches (#4748) ([2704d9c5](https://github.com/fable-compiler/Fable/commit/2704d9c5daab87eb1791c0ea1b6b8b06b987247c))
|
|
34
|
+
|
|
35
|
+
<strong><small>[View changes on Github](https://github.com/fable-compiler/Fable/compare/d6ae6bd3790b57b31941a118cdffaeb6a59155c3..a8d9bf5f0b7cc4560bb107963348e765695c22b6)</small></strong>
|
|
36
|
+
|
|
18
37
|
## 2.4.1 - 2026-07-09
|
|
19
38
|
|
|
20
39
|
### 🐞 Bug Fixes
|
package/Date.js
CHANGED
|
@@ -595,7 +595,10 @@ export function parseRaw(input) {
|
|
|
595
595
|
parseInt(timeParts[1] || "0", 10) * 60 +
|
|
596
596
|
parseFloat(timeParts[2] || "0");
|
|
597
597
|
if (m[3] != null && m[3].toUpperCase() === "PM" && hourPart < 12) {
|
|
598
|
-
timeInSeconds +=
|
|
598
|
+
timeInSeconds += 12 * 3600;
|
|
599
|
+
}
|
|
600
|
+
else if (m[3] != null && m[3].toUpperCase() === "AM" && hourPart === 12) {
|
|
601
|
+
timeInSeconds -= 12 * 3600;
|
|
599
602
|
}
|
|
600
603
|
}
|
|
601
604
|
if (m[4] != null) { // There's an offset, parse as UTC
|
|
@@ -784,19 +787,9 @@ export function addYears(d, v) {
|
|
|
784
787
|
return create(newYear, newMonth, newDay, hour(d), minute(d), second(d), millisecond(d), d.kind);
|
|
785
788
|
}
|
|
786
789
|
export function addMonths(d, v) {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
if (newMonth > 12) {
|
|
791
|
-
newMonth_ = newMonth % 12;
|
|
792
|
-
yearOffset = Math.floor(newMonth / 12);
|
|
793
|
-
newMonth = newMonth_;
|
|
794
|
-
}
|
|
795
|
-
else if (newMonth < 1) {
|
|
796
|
-
newMonth_ = 12 + newMonth % 12;
|
|
797
|
-
yearOffset = Math.floor(newMonth / 12) + (newMonth_ === 12 ? -1 : 0);
|
|
798
|
-
newMonth = newMonth_;
|
|
799
|
-
}
|
|
790
|
+
const totalMonths = month(d) + v;
|
|
791
|
+
const newMonth = ((totalMonths - 1) % 12 + 12) % 12 + 1;
|
|
792
|
+
const yearOffset = Math.floor((totalMonths - 1) / 12);
|
|
800
793
|
const newYear = year(d) + yearOffset;
|
|
801
794
|
const _daysInMonth = daysInMonth(newYear, newMonth);
|
|
802
795
|
const newDay = Math.min(_daysInMonth, day(d));
|
package/DateOffset.js
CHANGED
|
@@ -203,27 +203,18 @@ export function addTicks(d, v) {
|
|
|
203
203
|
return add(d, toFloat64(v / 10000n));
|
|
204
204
|
}
|
|
205
205
|
export function addYears(d, v) {
|
|
206
|
-
const
|
|
207
|
-
const
|
|
206
|
+
const d2 = new Date(d.getTime() + offset(d));
|
|
207
|
+
const newMonth = d2.getUTCMonth() + 1;
|
|
208
|
+
const newYear = d2.getUTCFullYear() + v;
|
|
208
209
|
const _daysInMonth = daysInMonth(newYear, newMonth);
|
|
209
|
-
const newDay = Math.min(_daysInMonth,
|
|
210
|
-
return create(newYear, newMonth, newDay,
|
|
210
|
+
const newDay = Math.min(_daysInMonth, d2.getUTCDate());
|
|
211
|
+
return create(newYear, newMonth, newDay, d2.getUTCHours(), d2.getUTCMinutes(), d2.getUTCSeconds(), d2.getUTCMilliseconds(), offset(d));
|
|
211
212
|
}
|
|
212
213
|
export function addMonths(d, v) {
|
|
213
214
|
const d2 = new Date(d.getTime() + offset(d));
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
if (newMonth > 12) {
|
|
218
|
-
newMonth_ = newMonth % 12;
|
|
219
|
-
yearOffset = Math.floor(newMonth / 12);
|
|
220
|
-
newMonth = newMonth_;
|
|
221
|
-
}
|
|
222
|
-
else if (newMonth < 1) {
|
|
223
|
-
newMonth_ = 12 + newMonth % 12;
|
|
224
|
-
yearOffset = Math.floor(newMonth / 12) + (newMonth_ === 12 ? -1 : 0);
|
|
225
|
-
newMonth = newMonth_;
|
|
226
|
-
}
|
|
215
|
+
const totalMonths = d2.getUTCMonth() + 1 + v;
|
|
216
|
+
const newMonth = ((totalMonths - 1) % 12 + 12) % 12 + 1;
|
|
217
|
+
const yearOffset = Math.floor((totalMonths - 1) / 12);
|
|
227
218
|
const newYear = d2.getUTCFullYear() + yearOffset;
|
|
228
219
|
const _daysInMonth = daysInMonth(newYear, newMonth);
|
|
229
220
|
const newDay = Math.min(_daysInMonth, d2.getUTCDate());
|
package/Directory.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This library doesn't depend on @types/node (to stay browser-compatible),
|
|
2
|
+
// so this Node.js built-in import can't be type-checked; the exported
|
|
3
|
+
// functions below still declare their own types for callers to rely on.
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import { existsSync, mkdirSync, statSync } from "node:fs";
|
|
6
|
+
export function exists(path) {
|
|
7
|
+
return existsSync(path) && statSync(path).isDirectory();
|
|
8
|
+
}
|
|
9
|
+
export function createDirectory(path) {
|
|
10
|
+
mkdirSync(path, { recursive: true });
|
|
11
|
+
}
|
package/Long.js
CHANGED
|
@@ -1,37 +1,28 @@
|
|
|
1
1
|
import { getPrefix, isValid } from "./Int32.js";
|
|
2
2
|
import { fromString } from "./BigInt.js";
|
|
3
3
|
import { Exception } from "./Util.js";
|
|
4
|
-
function
|
|
5
|
-
switch (
|
|
6
|
-
case
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
case 8: return unsigned ?
|
|
11
|
-
"1777777777777777777777" :
|
|
12
|
-
(isNegative ? "1000000000000000000000" : "777777777777777777777");
|
|
13
|
-
case 10: return unsigned ?
|
|
14
|
-
"18446744073709551615" :
|
|
15
|
-
(isNegative ? "9223372036854775808" : "9223372036854775807");
|
|
16
|
-
case 16: return unsigned ?
|
|
17
|
-
"FFFFFFFFFFFFFFFF" :
|
|
18
|
-
(isNegative ? "8000000000000000" : "7FFFFFFFFFFFFFFF");
|
|
19
|
-
default: throw new Exception("Invalid radix.");
|
|
4
|
+
function getRange(unsigned, bitsize) {
|
|
5
|
+
switch (bitsize) {
|
|
6
|
+
case 64: return unsigned ?
|
|
7
|
+
[0n, 18446744073709551615n] :
|
|
8
|
+
[-9223372036854775808n, 9223372036854775807n];
|
|
9
|
+
default: throw new Exception("Invalid bit size.");
|
|
20
10
|
}
|
|
21
11
|
}
|
|
22
|
-
export function parse(str, style, unsigned,
|
|
12
|
+
export function parse(str, style, unsigned, bitsize, radix) {
|
|
23
13
|
const res = isValid(str, style, radix);
|
|
24
14
|
if (res != null) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
let v = fromString(getPrefix(res.radix) + res.digits);
|
|
16
|
+
if (res.sign === "-") {
|
|
17
|
+
v = -v;
|
|
18
|
+
}
|
|
19
|
+
const [umin, umax] = getRange(true, bitsize);
|
|
20
|
+
if (!unsigned && res.radix !== 10 && v >= umin && v <= umax) {
|
|
21
|
+
v = BigInt.asIntN(bitsize, v);
|
|
22
|
+
}
|
|
23
|
+
const [min, max] = getRange(unsigned, bitsize);
|
|
24
|
+
if (v >= min && v <= max) {
|
|
25
|
+
return v;
|
|
35
26
|
}
|
|
36
27
|
}
|
|
37
28
|
throw new Exception(`The input string ${str} was not in a correct format.`);
|
package/package.json
CHANGED
package/quotation.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* and pattern matching helpers compatible with Fable's option convention
|
|
6
6
|
* (undefined = no match, value/tuple = match).
|
|
7
7
|
*/
|
|
8
|
+
import { ofArray } from "./List.js";
|
|
8
9
|
// ===================================================================
|
|
9
10
|
// Var: represents an F# quotation variable
|
|
10
11
|
// ===================================================================
|
|
@@ -74,11 +75,12 @@ export class ExprIfThenElse {
|
|
|
74
75
|
toJSON() { return ["IfThenElse", this.guard, this.thenExpr, this.elseExpr]; }
|
|
75
76
|
}
|
|
76
77
|
export class ExprCall {
|
|
77
|
-
constructor(instance, method, args) {
|
|
78
|
+
constructor(instance, method, args, declaringType = "") {
|
|
78
79
|
this.tag = "Call";
|
|
79
80
|
this.instance = instance;
|
|
80
81
|
this.method = method;
|
|
81
82
|
this.args = args;
|
|
83
|
+
this.declaringType = declaringType;
|
|
82
84
|
}
|
|
83
85
|
toJSON() { return ["Call", this.instance, this.method, this.args]; }
|
|
84
86
|
}
|
|
@@ -176,6 +178,10 @@ export class ExprVarSet {
|
|
|
176
178
|
export function mkValue(value, type) {
|
|
177
179
|
return new ExprValue(value, type);
|
|
178
180
|
}
|
|
181
|
+
export function mkNull(type) {
|
|
182
|
+
// A null-value node (no instance / null literal / empty option or list).
|
|
183
|
+
return new ExprValue(null, type);
|
|
184
|
+
}
|
|
179
185
|
export function mkVar(v) {
|
|
180
186
|
return new ExprVarExpr(v);
|
|
181
187
|
}
|
|
@@ -191,8 +197,8 @@ export function mkLet(v, value, body) {
|
|
|
191
197
|
export function mkIfThenElse(guard, thenExpr, elseExpr) {
|
|
192
198
|
return new ExprIfThenElse(guard, thenExpr, elseExpr);
|
|
193
199
|
}
|
|
194
|
-
export function mkCall(instance, method, args) {
|
|
195
|
-
return new ExprCall(instance, method, args);
|
|
200
|
+
export function mkCall(instance, method, args, declaringType = "") {
|
|
201
|
+
return new ExprCall(instance, method, args, declaringType);
|
|
196
202
|
}
|
|
197
203
|
export function mkSequential(first, second) {
|
|
198
204
|
return new ExprSequential(first, second);
|
|
@@ -273,10 +279,17 @@ export function isIfThenElse(expr) {
|
|
|
273
279
|
return undefined;
|
|
274
280
|
}
|
|
275
281
|
export function isCall(expr) {
|
|
276
|
-
if (expr instanceof ExprCall)
|
|
277
|
-
|
|
282
|
+
if (expr instanceof ExprCall) {
|
|
283
|
+
// "novalue" is the "no instance" sentinel, distinct from a genuine quoted null ("null").
|
|
284
|
+
const instance = (expr.instance instanceof ExprValue && expr.instance.type === "novalue") ? null : expr.instance;
|
|
285
|
+
// Must be a real FSharpList (not the raw array), same reasoning as isFieldGet below.
|
|
286
|
+
return [instance, expr.method, ofArray(expr.args)];
|
|
287
|
+
}
|
|
278
288
|
return undefined;
|
|
279
289
|
}
|
|
290
|
+
export function callDeclaringType(expr) {
|
|
291
|
+
return expr instanceof ExprCall ? expr.declaringType : "";
|
|
292
|
+
}
|
|
280
293
|
export function isSequential(expr) {
|
|
281
294
|
if (expr instanceof ExprSequential)
|
|
282
295
|
return [expr.first, expr.second];
|
|
@@ -303,8 +316,12 @@ export function isTupleGet(expr) {
|
|
|
303
316
|
return undefined;
|
|
304
317
|
}
|
|
305
318
|
export function isFieldGet(expr) {
|
|
306
|
-
|
|
307
|
-
|
|
319
|
+
// Third element mirrors PropertyGet's indexer-args slot, always empty here (never indexed).
|
|
320
|
+
// "novalue" is the "no instance" sentinel, distinct from a genuine quoted null ("null").
|
|
321
|
+
if (expr instanceof ExprFieldGet) {
|
|
322
|
+
const instance = (expr.expr instanceof ExprValue && expr.expr.type === "novalue") ? null : expr.expr;
|
|
323
|
+
return [instance, expr.fieldName, []];
|
|
324
|
+
}
|
|
308
325
|
return undefined;
|
|
309
326
|
}
|
|
310
327
|
// ===================================================================
|
|
@@ -555,7 +572,7 @@ export function substitute(expr, fn) {
|
|
|
555
572
|
return new ExprIfThenElse(sub(e.guard), sub(e.thenExpr), sub(e.elseExpr));
|
|
556
573
|
if (e instanceof ExprCall) {
|
|
557
574
|
const newInst = e.instance != null ? sub(e.instance) : e.instance;
|
|
558
|
-
return new ExprCall(newInst, e.method, e.args.map(sub));
|
|
575
|
+
return new ExprCall(newInst, e.method, e.args.map(sub), e.declaringType);
|
|
559
576
|
}
|
|
560
577
|
if (e instanceof ExprSequential)
|
|
561
578
|
return new ExprSequential(sub(e.first), sub(e.second));
|