wesl 0.6.49 → 0.7.1

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.
Files changed (94) hide show
  1. package/dist/index.d.ts +269 -215
  2. package/dist/index.js +2911 -1539
  3. package/package.json +6 -8
  4. package/src/AbstractElems.ts +81 -81
  5. package/src/Assertions.ts +5 -5
  6. package/src/BindIdents.ts +192 -306
  7. package/src/ClickableError.ts +3 -2
  8. package/src/Conditions.ts +2 -2
  9. package/src/LinkedWesl.ts +1 -1
  10. package/src/Linker.ts +4 -3
  11. package/src/LinkerUtil.ts +1 -1
  12. package/src/Logging.ts +165 -0
  13. package/src/LowerAndEmit.ts +278 -110
  14. package/src/ModuleResolver.ts +15 -25
  15. package/src/ParseError.ts +9 -0
  16. package/src/ParseWESL.ts +30 -94
  17. package/src/RawEmit.ts +1 -4
  18. package/src/Reflection.ts +1 -1
  19. package/src/Scope.ts +3 -0
  20. package/src/Span.ts +2 -0
  21. package/src/SrcMap.ts +208 -0
  22. package/src/Stream.ts +30 -0
  23. package/src/TransformBindingStructs.ts +2 -2
  24. package/src/Util.ts +1 -1
  25. package/src/debug/ASTtoString.ts +84 -135
  26. package/src/discovery/FindUnboundIdents.ts +14 -5
  27. package/src/index.ts +4 -0
  28. package/src/parse/ContentsHelpers.ts +70 -0
  29. package/src/parse/ExpressionUtil.ts +121 -0
  30. package/src/parse/Keywords.ts +12 -12
  31. package/src/parse/OperatorBinding.ts +146 -0
  32. package/src/parse/ParseAttribute.ts +272 -0
  33. package/src/parse/ParseCall.ts +77 -0
  34. package/src/parse/ParseControlFlow.ts +129 -0
  35. package/src/parse/ParseDirective.ts +105 -0
  36. package/src/parse/ParseExpression.ts +288 -0
  37. package/src/parse/ParseFn.ts +151 -0
  38. package/src/parse/ParseGlobalVar.ts +131 -0
  39. package/src/parse/ParseIdent.ts +77 -0
  40. package/src/parse/ParseImport.ts +160 -0
  41. package/src/parse/ParseLocalVar.ts +69 -0
  42. package/src/parse/ParseLoop.ts +112 -0
  43. package/src/parse/ParseModule.ts +116 -0
  44. package/src/parse/ParseSimpleStatement.ts +162 -0
  45. package/src/parse/ParseStatement.ts +215 -0
  46. package/src/parse/ParseStruct.ts +89 -0
  47. package/src/parse/ParseType.ts +71 -0
  48. package/src/parse/ParseUtil.ts +174 -0
  49. package/src/parse/ParseValueDeclaration.ts +130 -0
  50. package/src/parse/ParseWesl.ts +51 -0
  51. package/src/parse/ParsingContext.ts +93 -0
  52. package/src/parse/WeslStream.ts +63 -20
  53. package/src/parse/stream/CachingStream.ts +48 -0
  54. package/src/parse/stream/MatchersStream.ts +85 -0
  55. package/src/parse/stream/RegexHelpers.ts +38 -0
  56. package/src/test/BevyLink.test.ts +100 -0
  57. package/src/test/BindStdTypes.test.ts +110 -0
  58. package/src/test/{BindWESL.test.ts → BindWESLV2.test.ts} +21 -22
  59. package/src/test/BulkTests.test.ts +11 -12
  60. package/src/test/ConditionLinking.test.ts +107 -0
  61. package/src/test/ConditionalElif.test.ts +1 -13
  62. package/src/test/ConditionalTranslationCases.test.ts +5 -0
  63. package/src/test/ErrorLogging.test.ts +2 -2
  64. package/src/test/ImportCasesV2.test.ts +63 -0
  65. package/src/test/LinkFails.test.ts +69 -0
  66. package/src/test/LinkPackage.test.ts +1 -1
  67. package/src/test/Linker.test.ts +75 -2
  68. package/src/test/LogCatcher.ts +53 -0
  69. package/src/test/Mangling.test.ts +1 -1
  70. package/src/test/ParseComments.test.ts +1 -2
  71. package/src/test/{ParseConditions.test.ts → ParseConditionsV2.test.ts} +57 -49
  72. package/src/test/ParseErrorV2.test.ts +73 -0
  73. package/src/test/{ParseWESL.test.ts → ParseWeslV2.test.ts} +288 -370
  74. package/src/test/{ScopeWESL.test.ts → ScopeWESLV2.test.ts} +205 -176
  75. package/src/test/TestLink.ts +51 -51
  76. package/src/test/TestSetup.ts +9 -3
  77. package/src/test/TestUtil.ts +47 -77
  78. package/src/test/TrimmedMatch.ts +40 -0
  79. package/src/test/VirtualModules.test.ts +33 -2
  80. package/src/test/WeslDevice.test.ts +9 -2
  81. package/src/test/__snapshots__/ParseWeslV2.test.ts.snap +67 -0
  82. package/src/test-util.ts +7 -0
  83. package/src/WESLCollect.ts +0 -656
  84. package/src/parse/AttributeGrammar.ts +0 -232
  85. package/src/parse/ImportGrammar.ts +0 -195
  86. package/src/parse/WeslBaseGrammar.ts +0 -11
  87. package/src/parse/WeslExpression.ts +0 -231
  88. package/src/parse/WeslGrammar.ts +0 -739
  89. package/src/test/Expression.test.ts +0 -22
  90. package/src/test/ImportSyntaxCases.test.ts +0 -24
  91. package/src/test/ParseError.test.ts +0 -45
  92. package/src/test/Reflection.test.ts +0 -176
  93. package/src/test/TransformBindingStructs.test.ts +0 -238
  94. /package/src/test/{ParseElif.test.ts → ParseElifV2.test.ts} +0 -0
@@ -0,0 +1,9 @@
1
+ import type { Span } from "./Span.ts";
2
+
3
+ export class ParseError extends Error {
4
+ span: Span;
5
+ constructor(msg: string, span: Span) {
6
+ super(msg);
7
+ this.span = span;
8
+ }
9
+ }
package/src/ParseWESL.ts CHANGED
@@ -1,80 +1,67 @@
1
- import {
2
- type AppState,
3
- ParseError,
4
- type ParserInit,
5
- type Span,
6
- } from "mini-parse";
7
1
  import type {
8
2
  ConstAssertElem,
3
+ ContainerElem,
9
4
  ImportElem,
10
5
  ImportStatement,
11
6
  ModuleElem,
12
7
  } from "./AbstractElems.ts";
13
- import { throwClickableError } from "./ClickableError.ts";
14
8
  import { filterValidElements } from "./Conditions.ts";
15
9
  import { type FlatImport, flattenTreeImport } from "./FlattenTreeImport.ts";
16
- import { weslRoot } from "./parse/WeslGrammar.ts";
17
- import { WeslStream } from "./parse/WeslStream.ts";
18
- import {
19
- type Conditions,
20
- emptyScope,
21
- type Scope,
22
- type SrcModule,
23
- } from "./Scope.ts";
10
+ import type { ParseError } from "./ParseError.ts";
11
+ import { parseWesl } from "./parse/ParseWesl.ts";
12
+ import type { Conditions, Scope, SrcModule } from "./Scope.ts";
13
+ import type { Span } from "./Span.ts";
24
14
  import { errorHighlight, offsetToLineNumber } from "./Util.ts";
25
- import type { OpenElem } from "./WESLCollect.ts";
26
15
 
27
- /** result of a parse for one wesl module (e.g. one .wesl file)
16
+ /** Partial element being constructed during parsing. */
17
+ export type OpenElem<T extends ContainerElem = ContainerElem> = Pick<
18
+ T,
19
+ "kind" | "contents"
20
+ >;
21
+
22
+ /**
23
+ * Result of parsing one WESL module (e.g., one .wesl file).
28
24
  *
29
- * The parser constructs the AST constructed into three sections
30
- * for convenient access by the binding stage.
25
+ * The AST is constructed into three sections for the binding stage:
31
26
  * - import statements
32
27
  * - language elements (fn, struct, etc)
33
28
  * - scopes
34
- *
35
29
  */
36
30
  export interface WeslAST {
37
- /** source text for this module */
31
+ /** Source text for this module. */
38
32
  srcModule: SrcModule;
39
-
40
- /** root module element */
33
+ /** Root module element. */
41
34
  moduleElem: ModuleElem;
42
-
43
- /** root scope for this module */
35
+ /** Root scope for this module. */
44
36
  rootScope: Scope;
45
-
46
- /** imports found in this module */
37
+ /** Imports found in this module. */
47
38
  imports: ImportStatement[];
48
-
49
- /** module level const_assert statements */
39
+ /** Module level const_assert statements. */
50
40
  moduleAsserts?: ConstAssertElem[];
51
41
  }
52
42
 
53
- /** an extended version of the AST */
43
+ /** Extended AST with cached flattened imports. */
54
44
  export interface BindingAST extends WeslAST {
55
- /* a flattened version of the import statements constructed on demand from import trees, and cached here */
45
+ /** Flattened import statements (cached on demand). */
56
46
  _flatImports?: FlatImport[];
57
47
  }
58
48
 
59
- /** stable and unstable state used during parsing */
60
- export interface WeslParseState
61
- extends AppState<WeslParseContext, StableState> {
49
+ /** Stable and unstable state used during parsing. */
50
+ export interface WeslParseState {
62
51
  context: WeslParseContext;
63
52
  stable: StableState;
64
53
  }
65
54
 
66
- /** stable values used or accumulated during parsing */
55
+ /** Stable values used or accumulated during parsing. */
67
56
  export type StableState = WeslAST;
68
57
 
69
- /** unstable values used during parse collection */
58
+ /** Unstable values used during parse collection. */
70
59
  export interface WeslParseContext {
71
60
  scope: Scope; // current scope (points somewhere in rootScope)
72
61
  openElems: OpenElem[]; // elems that are collecting their contents
73
62
  }
74
63
 
75
- /**
76
- * An error when parsing WESL fails. Designed to be human-readable.
77
- */
64
+ /** Human-readable error when parsing WESL fails. */
78
65
  export class WeslParseError extends Error {
79
66
  span: Span;
80
67
  src: SrcModule;
@@ -84,69 +71,18 @@ export class WeslParseError extends Error {
84
71
  let message = `${opts.src.debugFilePath}:${lineNum}:${linePos}`;
85
72
  message += ` error: ${opts.cause.message}\n`;
86
73
  message += errorHighlight(source, opts.cause.span).join("\n");
87
- super(message, {
88
- cause: opts.cause,
89
- });
74
+ super(message, { cause: opts.cause });
90
75
  this.span = opts.cause.span;
91
76
  this.src = opts.src;
92
77
  }
93
78
  }
94
79
 
95
- /** Parse a WESL file. Throws on error. */
80
+ /** Parse a WESL file. */
96
81
  export function parseSrcModule(srcModule: SrcModule): WeslAST {
97
- const stream = new WeslStream(srcModule.src);
98
-
99
- const appState = blankWeslParseState(srcModule);
100
-
101
- const init: ParserInit = { stream, appState };
102
- try {
103
- const parseResult = weslRoot.parse(init);
104
- if (parseResult === null) {
105
- throw new Error("parseWESL failed");
106
- }
107
- } catch (e) {
108
- if (e instanceof ParseError) {
109
- const [lineNumber, lineColumn] = offsetToLineNumber(
110
- e.span[0],
111
- srcModule.src,
112
- );
113
- const error = new WeslParseError({ cause: e, src: srcModule });
114
- throwClickableError({
115
- url: srcModule.debugFilePath,
116
- text: srcModule.src,
117
- error,
118
- lineNumber,
119
- lineColumn,
120
- length: e.span[1] - e.span[0],
121
- });
122
- } else {
123
- throw e;
124
- }
125
- }
126
-
127
- return appState.stable as WeslAST;
128
- }
129
-
130
- export function blankWeslParseState(srcModule: SrcModule): WeslParseState {
131
- const rootScope = emptyScope(null);
132
- const moduleElem = null as any; // we'll fill this in later
133
- return {
134
- context: { scope: rootScope, openElems: [] },
135
- stable: { srcModule, imports: [], rootScope, moduleElem },
136
- };
137
- }
138
-
139
- export function syntheticWeslParseState(): WeslParseState {
140
- const srcModule: SrcModule = {
141
- modulePath: "package::test",
142
- debugFilePath: "./test.wesl",
143
- src: "",
144
- };
145
-
146
- return blankWeslParseState(srcModule);
82
+ return parseWesl(srcModule);
147
83
  }
148
84
 
149
- /** @return a flattened form of the import tree for convenience in binding idents. */
85
+ /** @return flattened form of import tree for binding idents. */
150
86
  export function flatImports(
151
87
  ast: BindingAST,
152
88
  conditions?: Conditions,
package/src/RawEmit.ts CHANGED
@@ -56,11 +56,8 @@ export function typeListToString(params: TypeTemplateParameter[]): string {
56
56
 
57
57
  export function typeParamToString(param?: TypeTemplateParameter): string {
58
58
  if (param === undefined) return "?";
59
- if (typeof param === "string") return param;
60
-
61
- if (param.kind === "expression") return contentsToString(param);
62
59
  if (param.kind === "type") return typeRefToString(param);
63
- assertUnreachable(param);
60
+ return expressionToString(param);
64
61
  }
65
62
 
66
63
  export function typeRefToString(t?: TypeRefElem): string {
package/src/Reflection.ts CHANGED
@@ -1,4 +1,3 @@
1
- import { matchOneOf } from "mini-parse";
2
1
  import type {
3
2
  BindingStructElem,
4
3
  NameElem,
@@ -11,6 +10,7 @@ import type {
11
10
  import { assertThat } from "./Assertions.ts";
12
11
  import type { TransformedAST, WeslJsPlugin } from "./Linker.ts";
13
12
  import { identElemLog } from "./LinkerUtil.ts";
13
+ import { matchOneOf } from "./parse/stream/RegexHelpers.ts";
14
14
  import type { RefIdent } from "./Scope.ts";
15
15
  import {
16
16
  multisampledTextureTypes,
package/src/Scope.ts CHANGED
@@ -38,6 +38,9 @@ export interface RefIdent extends IdentBase {
38
38
  refersTo?: Ident; // import or decl ident in scope to which this ident refers. undefined before binding
39
39
  std?: true; // true if this is a standard wgsl identifier (like sin, or u32)
40
40
 
41
+ /** True for identifiers in @if/@elif conditions. Binding skips these (for now). */
42
+ conditionRef?: true;
43
+
41
44
  // LATER consider tracking the current ast in BindIdents so that this field is unnecessary
42
45
  ast: WeslAST; // AST from module that contains this ident (to find imports during decl binding)
43
46
 
package/src/Span.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** A range, from start (inclusive) to end (exclusive). */
2
+ export type Span = readonly [number, number];
package/src/SrcMap.ts ADDED
@@ -0,0 +1,208 @@
1
+ /** A source map file, and a path for debug purposes. */
2
+ export interface SrcWithPath {
3
+ /** User friendly path */
4
+ path?: string;
5
+ text: string;
6
+ }
7
+
8
+ export interface SrcMapEntry {
9
+ src: SrcWithPath;
10
+ srcStart: number;
11
+ srcEnd: number;
12
+ destStart: number;
13
+ destEnd: number;
14
+ }
15
+
16
+ export interface SrcPosition {
17
+ src: SrcWithPath;
18
+ position: number;
19
+ }
20
+
21
+ /** map text ranges in multiple src texts to a single dest text */
22
+ export class SrcMap {
23
+ entries: SrcMapEntry[];
24
+ dest: SrcWithPath;
25
+
26
+ constructor(dest: SrcWithPath, entries: SrcMapEntry[] = []) {
27
+ this.dest = dest;
28
+ this.entries = entries;
29
+ }
30
+
31
+ /** add a new mapping from src to dest ranges (must be non-overlapping in destination) */
32
+ addEntries(newEntries: SrcMapEntry[]): void {
33
+ this.entries.push(...newEntries);
34
+ }
35
+
36
+ /** given positions in the dest string, return corresponding positions in the src strings */
37
+ mapPositions(...positions: number[]): SrcPosition[] {
38
+ return positions.map(p => this.destToSrc(p));
39
+ }
40
+
41
+ /** internally compress adjacent entries where possible */
42
+ compact(): void {
43
+ if (!this.entries.length) return;
44
+ let prev = this.entries[0];
45
+ const newEntries: SrcMapEntry[] = [prev];
46
+
47
+ for (let i = 1; i < this.entries.length; i++) {
48
+ const e = this.entries[i];
49
+ if (
50
+ e.src.path === prev.src.path &&
51
+ e.src.text === prev.src.text &&
52
+ prev.destEnd === e.destStart &&
53
+ prev.srcEnd === e.srcStart
54
+ ) {
55
+ // combine adjacent range entries into one
56
+ prev.destEnd = e.destEnd;
57
+ prev.srcEnd = e.srcEnd;
58
+ } else {
59
+ newEntries.push(e);
60
+ prev = e;
61
+ }
62
+ }
63
+ this.entries = newEntries;
64
+ }
65
+
66
+ /** sort in destination order */
67
+ sort(): void {
68
+ this.entries.sort((a, b) => a.destStart - b.destStart);
69
+ }
70
+
71
+ /**
72
+ * This SrcMap's destination is a src for the other srcmap,
73
+ * so combine the two and return the result.
74
+ */
75
+ merge(other: SrcMap): SrcMap {
76
+ if (other === this) return this;
77
+
78
+ const mappedEntries = other.entries.filter(
79
+ e => e.src.path === this.dest.path && e.src.text === this.dest.text,
80
+ );
81
+ if (mappedEntries.length === 0) {
82
+ console.log("other source map does not link to this one");
83
+ return other;
84
+ }
85
+ sortSrc(mappedEntries);
86
+ const newEntries = mappedEntries.map(e => {
87
+ const { src, position: srcStart } = this.destToSrc(e.srcStart);
88
+ const { src: endSrc, position: srcEnd } = this.destToSrc(e.srcEnd);
89
+ if (endSrc !== src) throw new Error("NYI, need to split");
90
+ const newEntry: SrcMapEntry = {
91
+ src,
92
+ srcStart,
93
+ srcEnd,
94
+ destStart: e.destStart,
95
+ destEnd: e.destEnd,
96
+ };
97
+ return newEntry;
98
+ });
99
+
100
+ const otherSources = other.entries.filter(
101
+ e => e.src.path !== this.dest.path || e.src.text !== this.dest.text,
102
+ );
103
+
104
+ const newMap = new SrcMap(other.dest, [...otherSources, ...newEntries]);
105
+ newMap.sort();
106
+ return newMap;
107
+ }
108
+
109
+ /** @return the source position corresponding to a provided destination position */
110
+ destToSrc(destPos: number): SrcPosition {
111
+ const entry = this.entries.find(
112
+ e => e.destStart <= destPos && e.destEnd >= destPos,
113
+ );
114
+ if (!entry) {
115
+ return { src: this.dest, position: destPos };
116
+ }
117
+ return {
118
+ src: entry.src,
119
+ position: entry.srcStart + destPos - entry.destStart,
120
+ };
121
+ }
122
+ }
123
+
124
+ /** sort entries in place by src start position */
125
+ function sortSrc(entries: SrcMapEntry[]): void {
126
+ entries.sort((a, b) => a.srcStart - b.srcStart);
127
+ }
128
+
129
+ /** Incrementally append to a string, tracking source references */
130
+ export class SrcMapBuilder {
131
+ #fragments: string[] = [];
132
+ #destLength = 0;
133
+ #entries: SrcMapEntry[] = [];
134
+ source: SrcWithPath;
135
+
136
+ constructor(source: SrcWithPath) {
137
+ this.source = source;
138
+ }
139
+
140
+ /** append a string fragment to the destination string */
141
+ add(fragment: string, srcStart: number, srcEnd: number): void {
142
+ const destStart = this.#destLength;
143
+ this.#destLength += fragment.length;
144
+ const destEnd = this.#destLength;
145
+
146
+ this.#fragments.push(fragment);
147
+ this.#entries.push({
148
+ src: this.source,
149
+ srcStart,
150
+ srcEnd,
151
+ destStart,
152
+ destEnd,
153
+ });
154
+ }
155
+
156
+ /**
157
+ * Append a fragment to the destination string,
158
+ * mapping source to the previous,
159
+ * and guessing that the source fragment is just as long as the dest fragment.
160
+ */
161
+ appendNext(fragment: string): void {
162
+ const lastEnd = this.#entries.at(-1)?.destEnd ?? 0;
163
+ this.add(fragment, lastEnd, lastEnd + fragment.length);
164
+ }
165
+
166
+ addSynthetic(
167
+ fragment: string,
168
+ syntheticSource: string,
169
+ srcStart: number,
170
+ srcEnd: number,
171
+ ): void {
172
+ const destStart = this.#destLength;
173
+ this.#destLength += fragment.length;
174
+ const destEnd = this.#destLength;
175
+
176
+ this.#fragments.push(fragment);
177
+ this.#entries.push({
178
+ src: { text: syntheticSource },
179
+ srcStart,
180
+ srcEnd,
181
+ destStart,
182
+ destEnd,
183
+ });
184
+ }
185
+
186
+ /** append a synthetic newline, mapped to previous source location */
187
+ addNl(): void {
188
+ const lastEntry = this.#entries.at(-1) ?? { srcStart: 0, srcEnd: 0 };
189
+ const { srcStart, srcEnd } = lastEntry;
190
+ this.add("\n", srcStart, srcEnd);
191
+ }
192
+
193
+ /** copy a string fragment from the src to the destination string */
194
+ addCopy(srcStart: number, srcEnd: number): void {
195
+ const fragment = this.source.text.slice(srcStart, srcEnd);
196
+ this.add(fragment, srcStart, srcEnd);
197
+ }
198
+
199
+ /** return a SrcMap */
200
+ static build(builders: SrcMapBuilder[]): SrcMap {
201
+ const map = new SrcMap(
202
+ { text: builders.map(b => b.#fragments.join("")).join("") },
203
+ builders.flatMap(b => b.#entries),
204
+ );
205
+ map.compact();
206
+ return map;
207
+ }
208
+ }
package/src/Stream.ts ADDED
@@ -0,0 +1,30 @@
1
+ import type { Span } from "./Span.ts";
2
+
3
+ /**
4
+ * Interface for a tokenizer. Returns a "next token", and can be reset to
5
+ * previously saved positions (checkpoints).
6
+ */
7
+ export interface Stream<T extends Token> {
8
+ /** Returns the current position */
9
+ checkpoint(): number;
10
+ /** Restores a position */
11
+ reset(position: number): void;
12
+ /**
13
+ * Returns the next token, or `null` if the end of the stream has been reached.
14
+ * Always leaves `checkpoint` right after the token.
15
+ */
16
+ nextToken(): T | null;
17
+ /** src text */
18
+ src: string;
19
+ }
20
+
21
+ /** A text token */
22
+ export interface Token {
23
+ kind: string;
24
+ text: string;
25
+ span: Span;
26
+ }
27
+
28
+ export interface TypedToken<Kind extends string> extends Token {
29
+ kind: Kind;
30
+ }
@@ -1,4 +1,3 @@
1
- import { tracing } from "mini-parse";
2
1
  import type {
3
2
  AbstractElem,
4
3
  AttributeElem,
@@ -13,6 +12,7 @@ import type {
13
12
  } from "./AbstractElems.ts";
14
13
  import type { TransformedAST, WeslJsPlugin } from "./Linker.ts";
15
14
  import { visitAst } from "./LinkerUtil.ts";
15
+ import { debug } from "./Logging.ts";
16
16
  import { findDecl } from "./LowerAndEmit.ts";
17
17
  import { minimallyMangledName } from "./Mangler.ts";
18
18
  import {
@@ -307,7 +307,7 @@ export function transformBindingReference(
307
307
  const refName = memberRef.member.name;
308
308
  const structMember = struct.members.find(m => m.name.name === refName)!;
309
309
  if (!structMember || !structMember.mangledVarName) {
310
- if (tracing) console.log(`missing mangledVarName for ${refName}`);
310
+ if (debug) console.log(`missing mangledVarName for ${refName}`);
311
311
  return { kind: "synthetic", text: refName };
312
312
  }
313
313
  const { extraComponents } = memberRef;
package/src/Util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Span } from "mini-parse";
1
+ import type { Span } from "./Span.ts";
2
2
 
3
3
  export function multiKeySet<A, B, V>(
4
4
  m: Map<A, Map<B, V>>,