Repository: tact-lang/tact

Issue:
Unify FunC and Tact benchmarks: escrow


Discussion:

Solution patch:
diff --git a/dev-docs/CHANGELOG.md b/dev-docs/CHANGELOG.md
index 744b0777..abee28a4 100644
--- a/dev-docs/CHANGELOG.md
+++ b/dev-docs/CHANGELOG.md
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Language features
 
+- Fixed incorrect error message for bounced messages: PR [#2932](https://github.com/tact-lang/tact/pull/2932)
+- Added compile-time map literals: PR [#2881](https://github.com/tact-lang/tact/pull/2881)
+- Added the `inMsg()` built-in function as an optimized version of `msg.toSlice()`: PR [#2850](https://github.com/tact-lang/tact/pull/2850)
 - Compiler now generates more efficient code for structure fields serialization: PR [#2836](https://github.com/tact-lang/tact/pull/2836)
 - Compiler now generates more efficient code for `Address?` fields deserialization: PR [#2834](https://github.com/tact-lang/tact/pull/2834)
 - Optimized `self.notify`, `self.reply`, and `self.forward` in `BaseTrait` by using message directly where possible and avoiding unnecessary use of alias: PR [#2515](https://github.com/tact-lang/tact/pull/2515)
@@ -19,7 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Standard Library
 
-- Added `inMsg()` function as an optimized version of `msg.toSlice()`: PR [#2850](https://github.com/tact-lang/tact/pull/2850)
+- Improved gas consumption of the `cashback` function: PR [#2882](https://github.com/tact-lang/tact/pull/2882)
+- Improved gas efficiency for the `BaseTrait` functions: PR [#2913](https://github.com/tact-lang/tact/pull/2913)
 
 ### Tooling
 
@@ -37,6 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [Petr Makhnev](https://github.com/i582)
 - [Novus Nota](https://github.com/novusnota)
 - [skywardboundd](https://github.com/skywardboundd)
+- [verytactical](https://github.com/verytactical)
+- [Anton Trunov](https://github.com/anton-trunov)
 
 ## [1.6.6] - 2025-04-16
 
diff --git a/docs/src/content/docs/book/bounced.mdx b/docs/src/content/docs/book/bounced.mdx
index 5cf3a1af..7ebfcfd8 100644
--- a/docs/src/content/docs/book/bounced.mdx
+++ b/docs/src/content/docs/book/bounced.mdx
@@ -7,7 +7,9 @@ When a contract sends a message with the flag `bounce` set to `true{:tact}`, and
 
 ## Caveats
 
-Currently, bounced messages in TON have only 224 usable data bits in the message body and don't have any references. This means that you can't recover much of the data from the bounced message. This is a limitation of the TON blockchain and will be fixed in the future. Tact helps you to check if your message fits the limit, and if it doesn't, suggests using a special type constructor `bounced<M>{:tact}` for the bounced message receiver, which constructs a partial representation of the message that fits within the required limits.
+Currently, bounced messages in TON can have at most 256 bits of payload and no references, but the amount of useful data bits when handling messages in `bounced` receivers is at most 224, since the first 32 bits are occupied by the message opcode. This means that you can't recover much of the data from the bounced message, for instance you cannot fit an address into a bounced message, since regular internal addresses need 267 bits to be represented.
+
+Tact helps you to check if your message fits the limit, and if it doesn't, suggests using a special type constructor `bounced<M>{:tact}` for the bounced message receiver, which constructs a partial representation of the message that fits within the required limits. Only the first message fields that fit into the 224 bits will be available to use in bounced message receivers. Sometimes you need to rearrange the order of the fields in your message so the relevant ones would fit into the limit.
 
 ## Bounced message receiver
 
diff --git a/docs/src/content/docs/book/expressions.mdx b/docs/src/content/docs/book/expressions.mdx
index 8709164a..f27eaa8d 100644
--- a/docs/src/content/docs/book/expressions.mdx
+++ b/docs/src/content/docs/book/expressions.mdx
@@ -170,6 +170,31 @@ if (var != null) {
 
 Read more about working with `null{:tact}` on the dedicated page: [Optionals](/book/optionals).
 
+### Map literals
+
+<Badge text="Available since Tact 1.6.7" variant="tip" size="medium"/><p/>
+
+Map literals create maps by enclosing a comma-delimited list of zero or more predefined key-value pairs in curly braces.
+
+```tact
+// A compile-time map literal
+let myMap: map<Int as uint8, Int as int13> = map<Int as uint8, Int as int13> {
+    // Key expression: Value expression
+    1 + 2: 10 * pow2(3), // key 3, value 80
+    1 + 3: 20 * pow2(4), // key 4, value 320
+};
+myMap.get(3)!!; // 80
+myMap.get(4)!!; // 320
+```
+
+Read more: [Initialize a map with a literal](/book/maps#initialize).
+
+:::note
+
+  Support for runtime initialization values that are not resolved at [compile-time](/ref/core-comptime) is planned for future Tact releases.
+
+:::
+
 ## Identifiers
 
 An identifier is a sequence of characters in the code that _identifies_ a [variable](/book/statements#let), [constant](/book/constants), [map](/book/maps), a [function](/book/functions), as well as a [Struct][s], [Message][m], [contract](/book/contracts), [trait](/book/types#traits), or their fields and methods. Identifiers are case-sensitive and not quoted.
diff --git a/docs/src/content/docs/book/learn-tact-in-y-minutes.mdx b/docs/src/content/docs/book/learn-tact-in-y-minutes.mdx
index 63cd9539..fb41ef10 100644
--- a/docs/src/content/docs/book/learn-tact-in-y-minutes.mdx
+++ b/docs/src/content/docs/book/learn-tact-in-y-minutes.mdx
@@ -244,7 +244,8 @@ fun showcase() {
     // keys of type K with corresponding values of type V.
 
     // A map of Int keys to Int values.
-    // Type ascription is mandatory.
+    // Type ascription is mandatory unless you use a map literal for initialization.
+    // See below for more info.
     let myMap: map<Int, Int> = emptyMap();
 
     // Maps have a number of built-in methods.
@@ -259,6 +260,14 @@ fun showcase() {
     myMap.exists(0);           // false, there is no entry under key 0
     myMap.isEmpty();           // true, there is no other entries
 
+    // Since Tact 1.6.7 statically known `set`s can be replaced by map literals.
+    // Because the map is created at compile-time, it doesn't consume as much gas.
+    let myMap2: map<Int as uint8, Int as int13> = map<Int as uint8, Int as int13> {
+        // Key expression: Value expression
+        1 + 2: 10 * pow2(3), // key 3, value 80
+        1 + 3: 20 * pow2(4), // key 4, value 320
+    };
+
     // In most cases, to compare two maps it's sufficient to use the shallow
     // comparison via the equality == and inequality != operators.
     myMap == emptyMap(); // true
@@ -515,6 +524,9 @@ contract MyContract() {
               // and the value of the currently extended type inside
               // the extension function. See the "Functions" section below for more.
 
+        // Map literals.
+        map<Int, Int as coins> { 11: 11 };
+
         // Identifiers, with usual naming conventions:
         // They may contain Latin lowercase letters `a-z`,
         // Latin uppercase letters `A-Z`, underscores `_`,
diff --git a/docs/src/content/docs/book/maps.mdx b/docs/src/content/docs/book/maps.mdx
index 99bd514c..85d4a74c 100644
--- a/docs/src/content/docs/book/maps.mdx
+++ b/docs/src/content/docs/book/maps.mdx
@@ -118,6 +118,85 @@ contract Example {
 
 Note that [persistent state variables](/book/contracts#variables) of type `map<K, V>{:tact}` are initialized empty by default and do not need default values or initialization in the [`init(){:tact}` function](/book/contracts#init-function).
 
+### Initialize with a map literal {#initialize}
+
+<Badge text="Available since Tact 1.6.7" variant="tip" size="medium"/><p/>
+
+```tact
+// K and V correspond to the key and value types of the target map
+map<K, V> { key1: value1, key2: value2, /* ... */, keyN: valueN };
+```
+
+Map literals are expressions that provide a concise way to create maps. First goes the `map<K, V>{:tact}` type expression, then a curly-brace enclosed comma-delimited list of zero or more predefined key-value pairs.
+
+```tact
+// Declaring a map as a local variable via a map literal expression
+let myMap: map<Int as uint8, Int as uint8> = map<Int as uint8, Int as uint8> {
+    // Key: Value
+    1: 100, // key 1, value 100
+    2: 200, // key 2, value 200
+};
+
+// However, if you specify the type of ascription in the let statement,
+// it should exactly match the one in the map literal, or it will not compile.
+let mismatch: map<Address, Cell> = map<Address, Int> {}; // COMPILATION ERROR! Type mismatch
+```
+
+Empty map literals are also supported. They produce the same value as the [`emptyMap(){:tact}`](#emptymap) function but allow omitting type ascriptions.
+
+```tact
+let myMap2: map<Int, Cell> = emptyMap();
+
+myMap == myMap2; // true
+```
+
+When using the same key for the multiple entries, the last occurrence of each key-value pair takes precedence. That is, if there are multiple entries with the same key, only the last one will be used.
+
+```tact
+let allTheSame: map<Int as int16, Int as int16> = map<Int as int16, Int as int16> {
+    1: 100,
+    1: 200,
+    1: 300,
+    1: 400,
+};
+allTheSame.get(1)!!; // 400
+```
+
+Both keys and values can be arbitrary compile-time expressions as long as their resulting types match the respective key-value types of the map literal.
+
+```tact
+fun getExchangesList(): map<Address, Cell> {
+    return map<Address, Cell> {
+        address("UQD5vcDeRhwaLgAvralVC7sJXI-fc2aNcMUXqcx-BQ-OWi5c"): "One Known eXchange".asCell(),
+        address("UQABGo8KCza3ea8DNHMnSWZmbRzW-05332eTdfvW-XDQEmnJ"): "yRacket".asCell(),
+    };
+}
+```
+
+Because maps are represented as cells with binary data, `Int` and `Uint` types must be used with [storage annotations](/book/integers#serialization).
+
+```tact
+let smartMoney: map<Int as uint8, Int as coins> = map<Int as uint8, Int as coins> {};
+
+// Notice that annotations are checked and
+let tryAndSerializeMe = map<Int as uint1, Int as uint8> {
+    1: 100,
+    2: 200, // COMPILATION ERROR! bitLength is too small
+};
+
+// If you explicitly specify the type of ascription in the let statement,
+// it should exactly match the one in the map literal, or it will not compile.
+let nope: map<Int as uint8, Int as coins> = map<Int as int257, Int as varuint32> {};
+//                                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+//                                          COMPILATION ERROR! Type mismatch
+```
+
+:::note
+
+  Support for runtime initialization values that are not resolved at [compile-time](/ref/core-comptime) is planned for future Tact releases.
+
+:::
+
 ### Set values, `.set()` {#set}
 
 ```tact
diff --git a/knip.json b/knip.json
index 760ab4fc..f70977b6 100644
--- a/knip.json
+++ b/knip.json
@@ -29,7 +29,8 @@
     "src/utils/array.ts",
     ".github/workflows/tact*.yml",
     ".github/actions/**/*.yml",
-    "src/fmt/cst/cst-parser.ts"
+    "src/fmt/cst/cst-parser.ts",
+    "src/pipeline/ast-factory-store.ts"
   ],
   "ignoreDependencies": ["@tact-lang/ton-abi", "husky", "tsconfig-paths"]
 }
diff --git a/src/abi/map.ts b/src/abi/map.ts
index 2b405a3c..712457a3 100644
--- a/src/abi/map.ts
+++ b/src/abi/map.ts
@@ -1,6 +1,6 @@
 import type { CompilerContext } from "@/context/context";
 import type { SrcInfo } from "@/grammar";
-import type { TypeRef } from "@/types/types";
+import type { TypeRef, TypeRefMap } from "@/types/types";
 import { printTypeRef } from "@/types/types";
 import type { WriterContext } from "@/generator/Writer";
 import { ops } from "@/generator/writers/ops";
@@ -125,6 +125,58 @@ function handleStructOrOtherValue(
     throwCompilationError(`"${t.name}" can't be value of a map`, ref);
 }
 
+function generateSet(
+    ctx: WriterContext,
+    ref: SrcInfo,
+    self: TypeRefMap,
+    value: TypeRef | undefined,
+    resolved: string[],
+) {
+    const [selfCode, keyCode, valueCode] = resolved;
+    const argsCode = `${keyCode}, ${valueCode}`;
+    const { bits, kind } = resolveMapKeyBits(self, ref);
+    if (self.value === "Int") {
+        let vBits = 257;
+        let vKind = "int";
+        if (self.valueAs?.startsWith("int")) {
+            vBits = parseInt(self.valueAs.slice(3), 10);
+        } else if (self.valueAs?.startsWith("uint")) {
+            vBits = parseInt(self.valueAs.slice(4), 10);
+            vKind = "uint";
+        } else if (self.valueAs?.startsWith("coins")) {
+            vKind = "coins";
+            ctx.used(`__tact_dict_set_${kind}_${vKind}`);
+            return `${selfCode}~__tact_dict_set_${kind}_${vKind}(${bits}, ${argsCode})`;
+        } else if (self.valueAs?.startsWith("var")) {
+            vKind = self.valueAs;
+            ctx.used(`__tact_dict_set_${kind}_${vKind}`);
+            return `${selfCode}~__tact_dict_set_${kind}_${vKind}(${bits}, ${argsCode})`;
+        }
+        ctx.used(`__tact_dict_set_${kind}_${vKind}`);
+        return `${selfCode}~__tact_dict_set_${kind}_${vKind}(${bits}, ${argsCode}, ${vBits})`;
+    } else if (self.value === "Bool") {
+        ctx.used(`__tact_dict_set_${kind}_int`);
+        return `${selfCode}~__tact_dict_set_${kind}_int(${bits}, ${argsCode}, 1)`;
+    } else if (self.value === "Cell") {
+        ctx.used(`__tact_dict_set_${kind}_cell`);
+        return `${selfCode}~__tact_dict_set_${kind}_cell(${bits}, ${argsCode})`;
+    } else if (self.value === "Address") {
+        ctx.used(`__tact_dict_set_${kind}_slice`);
+        return `${selfCode}~__tact_dict_set_${kind}_slice(${bits}, ${argsCode})`;
+    } else {
+        return handleStructOrOtherValue(
+            self,
+            value!,
+            resolved,
+            ctx,
+            ref,
+            bits,
+            kind,
+            "set",
+        );
+    }
+}
+
 // The fully refactored MapFunctions object
 export const MapFunctions: ReadonlyMap<string, AbiFunction> = new Map([
     [
@@ -158,48 +210,8 @@ export const MapFunctions: ReadonlyMap<string, AbiFunction> = new Map([
                 checkMapType(self, ref);
 
                 const resolved = exprs.map((v) => writeExpression(v, ctx));
-                const { bits, kind } = resolveMapKeyBits(self, ref);
 
-                if (self.value === "Int") {
-                    let vBits = 257;
-                    let vKind = "int";
-                    if (self.valueAs?.startsWith("int")) {
-                        vBits = parseInt(self.valueAs.slice(3), 10);
-                    } else if (self.valueAs?.startsWith("uint")) {
-                        vBits = parseInt(self.valueAs.slice(4), 10);
-                        vKind = "uint";
-                    } else if (self.valueAs?.startsWith("coins")) {
-                        vKind = "coins";
-                        ctx.used(`__tact_dict_set_${kind}_${vKind}`);
-                        return `${resolved[0]}~__tact_dict_set_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]})`;
-                    } else if (self.valueAs?.startsWith("var")) {
-                        vKind = self.valueAs;
-                        ctx.used(`__tact_dict_set_${kind}_${vKind}`);
-                        return `${resolved[0]}~__tact_dict_set_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]})`;
-                    }
-                    ctx.used(`__tact_dict_set_${kind}_${vKind}`);
-                    return `${resolved[0]}~__tact_dict_set_${kind}_${vKind}(${bits}, ${resolved[1]}, ${resolved[2]}, ${vBits})`;
-                } else if (self.value === "Bool") {
-                    ctx.used(`__tact_dict_set_${kind}_int`);
-                    return `${resolved[0]}~__tact_dict_set_${kind}_int(${bits}, ${resolved[1]}, ${resolved[2]}, 1)`;
-                } else if (self.value === "Cell") {
-                    ctx.used(`__tact_dict_set_${kind}_cell`);
-                    return `${resolved[0]}~__tact_dict_set_${kind}_cell(${bits}, ${resolved[1]}, ${resolved[2]})`;
-                } else if (self.value === "Address") {
-                    ctx.used(`__tact_dict_set_${kind}_slice`);
-                    return `${resolved[0]}~__tact_dict_set_${kind}_slice(${bits}, ${resolved[1]}, ${resolved[2]})`;
-                } else {
-                    return handleStructOrOtherValue(
-                        self,
-                        value!,
-                        resolved,
-                        ctx,
-                        ref,
-                        bits,
-                        kind,
-                        "set",
-                    );
-                }
+                return generateSet(ctx, ref, self, value, resolved);
             },
         },
     ],
diff --git a/src/ast/ast-printer.ts b/src/ast/ast-printer.ts
index 33ae029f..f40e2ff0 100644
--- a/src/ast/ast-printer.ts
+++ b/src/ast/ast-printer.ts
@@ -199,6 +199,30 @@ export const ppAstAddress = ({ value }: Ast.Address) =>
 export const ppAstCell = ({ value }: Ast.Cell) => `cell("${value.toString()}")`;
 export const ppAstSlice = ({ value }: Ast.Slice) =>
     `slice("${value.toString()}")`;
+export const ppAstMapLiteral = ({ type, fields }: Ast.MapLiteral) => {
+    const key = ppAstTypeIdWithStorage(type.keyType, type.keyStorageType);
+    const value = ppAstTypeIdWithStorage(type.valueType, type.valueStorageType);
+    const exprs = fields
+        .map((expr) => {
+            const key = ppAstExpression(expr.key);
+            const value = ppAstExpression(expr.value);
+            return `${key}: ${value}`;
+        })
+        .join(", ");
+    return `map<${key}, ${value}> { ${exprs} }`;
+};
+export const ppAstSetLiteral = ({
+    valueType,
+    valueStorageType,
+    fields,
+}: Ast.SetLiteral) => {
+    const type = ppAstTypeIdWithStorage(valueType, valueStorageType);
+    const exprs = fields.map((expr) => ppAstExpression(expr)).join(", ");
+    return `set<${type}> { ${exprs} }`;
+};
+export const ppAstMapValue = (_: Ast.MapValue): string => {
+    return `map<...>(...)`;
+};
 
 export const ppAstStaticCall = ({ function: func, args }: Ast.StaticCall) => {
     return `${ppAstId(func)}(${ppExprArgs(args)})`;
@@ -268,6 +292,9 @@ export const ppAstExpressionNested = makeVisitor<Ast.Expression>()({
     address: ppLeaf(ppAstAddress),
     cell: ppLeaf(ppAstCell),
     slice: ppLeaf(ppAstSlice),
+    map_literal: ppLeaf(ppAstMapLiteral),
+    set_literal: ppLeaf(ppAstSetLiteral),
+    map_value: ppLeaf(ppAstMapValue),
 
     method_call: ppAstMethodCall,
     field_access: ppAstFieldAccess,
@@ -916,6 +943,9 @@ export const ppAstNode: Printer<Ast.AstNode> = makeVisitor<Ast.AstNode>()({
     method_call: exprNode(ppAstExpression),
     static_call: exprNode(ppAstExpression),
     struct_instance: exprNode(ppAstExpression),
+    map_literal: exprNode(ppAstExpression),
+    set_literal: exprNode(ppAstExpression),
+    map_value: exprNode(ppAstExpression),
     struct_value: exprNode(ppAstStructValue),
     init_of: exprNode(ppAstExpression),
     code_of: exprNode(ppAstExpression),
diff --git a/src/ast/ast.ts b/src/ast/ast.ts
index 453f9cdd..6d006a65 100644
--- a/src/ast/ast.ts
+++ b/src/ast/ast.ts
@@ -391,6 +391,8 @@ export type Expression =
     | Id
     | InitOf
     | CodeOf
+    | MapLiteral
+    | SetLiteral
     | Literal;
 
 export type Literal =
@@ -401,6 +403,7 @@ export type Literal =
     | Address
     | Cell
     | Slice
+    | MapValue
     | StructValue;
 
 export type BinaryOperation =
@@ -476,6 +479,28 @@ export type StructInstance = {
     readonly loc: SrcInfo;
 };
 
+export type MapLiteral = {
+    readonly kind: "map_literal";
+    readonly type: MapType;
+    readonly fields: readonly MapField[];
+    readonly id: number;
+    readonly loc: SrcInfo;
+};
+
+export type MapField = {
+    readonly key: Expression;
+    readonly value: Expression;
+};
+
+export type SetLiteral = {
+    readonly kind: "set_literal";
+    readonly valueType: TypeId;
+    readonly valueStorageType: Id | undefined;
+    readonly fields: readonly Expression[];
+    readonly id: number;
+    readonly loc: SrcInfo;
+};
+
 export type StructFieldInitializer = {
     readonly kind: "struct_field_initializer";
     readonly field: Id;
@@ -611,6 +636,14 @@ export type Slice = {
     readonly loc: SrcInfo;
 };
 
+export type MapValue = {
+    readonly kind: "map_value";
+    readonly bocHex: string | undefined;
+    readonly type: MapType;
+    readonly id: number;
+    readonly loc: SrcInfo;
+};
+
 export type StructValue = {
     readonly kind: "struct_value";
     readonly type: Id;
diff --git a/src/ast/getAstSchema.ts b/src/ast/getAstSchema.ts
index 90617873..afc2c6ac 100644
--- a/src/ast/getAstSchema.ts
+++ b/src/ast/getAstSchema.ts
@@ -533,6 +533,30 @@ export const getAstSchema = (
                 initializer,
                 loc: toSrcInfo(loc),
             }),
+        MapLiteral: (
+            type: Ast.MapType,
+            fields: readonly Ast.MapField[],
+            loc: Loc,
+        ): Ast.MapLiteral =>
+            createNode<Ast.MapLiteral>({
+                kind: "map_literal",
+                type,
+                fields,
+                loc: toSrcInfo(loc),
+            }),
+        SetLiteral: (
+            valueType: Ast.TypeId,
+            valueStorageType: Ast.Id | undefined,
+            fields: readonly Ast.Expression[],
+            loc: Loc,
+        ): Ast.SetLiteral =>
+            createNode<Ast.SetLiteral>({
+                kind: "set_literal",
+                valueType,
+                valueStorageType,
+                fields,
+                loc: toSrcInfo(loc),
+            }),
         InitOf: (
             contract: Ast.Id,
             args: Ast.Expression[],
diff --git a/src/ast/iterators.ts b/src/ast/iterators.ts
index c1efa8e6..1b5e2f91 100644
--- a/src/ast/iterators.ts
+++ b/src/ast/iterators.ts
@@ -1,4 +1,5 @@
 import type { AstNode } from "@/ast/ast";
+import { throwInternalCompilerError } from "@/error/errors";
 
 /**
  * Recursively iterates over each node in an AstNode and applies a callback to each AST element.
@@ -329,10 +330,20 @@ export function traverseAndCheck(
         case "address":
         case "cell":
         case "slice":
+        case "map_value":
             break;
         case "typed_parameter":
             traverseAndCheck(node.name, callback);
             traverseAndCheck(node.type, callback);
             break;
+        case "set_literal":
+            throwInternalCompilerError("Set literals are not supported");
+            break;
+        case "map_literal":
+            node.fields.forEach((e) => {
+                traverseAndCheck(e.key, callback);
+                traverseAndCheck(e.value, callback);
+            });
+            break;
     }
 }
diff --git a/src/ast/util.ts b/src/ast/util.ts
index bb416a0e..56971617 100644
--- a/src/ast/util.ts
+++ b/src/ast/util.ts
@@ -120,6 +120,20 @@ export const getAstUtil = ({ createNode }: FactoryAst) => {
         return result as Ast.StructValue;
     }
 
+    function makeMapValue(
+        bocHex: string | undefined,
+        type: Ast.MapType,
+        loc: SrcInfo,
+    ): Ast.MapValue {
+        const result = createNode({
+            kind: "map_value",
+            bocHex,
+            type,
+            loc,
+        });
+        return result as Ast.MapValue;
+    }
+
     return {
         makeUnaryExpression,
         makeBinaryExpression,
@@ -131,6 +145,7 @@ export const getAstUtil = ({ createNode }: FactoryAst) => {
         makeAddressLiteral,
         makeStructFieldValue,
         makeStructValue,
+        makeMapValue,
     };
 };
 
diff --git a/src/benchmarks/escrow/escrow.spec.ts b/src/benchmarks/escrow/escrow.spec.ts
index d89167f2..7333b0c9 100644
--- a/src/benchmarks/escrow/escrow.spec.ts
+++ b/src/benchmarks/escrow/escrow.spec.ts
@@ -1,6 +1,6 @@
 import "@ton/test-utils";
 import type { Address } from "@ton/core";
-import { Cell, beginCell, toNano, contractAddress, SendMode } from "@ton/core";
+import { Cell, beginCell, toNano, contractAddress } from "@ton/core";
 
 import type { Sender } from "@ton/core";
 import { Blockchain } from "@ton/sandbox";
@@ -11,6 +11,8 @@ import {
     generateCodeSizeResults,
     getUsedGas,
     printBenchmarkTable,
+    type BenchmarkResult,
+    type CodeSizeResult,
 } from "@/benchmarks/utils/gas";
 import { join, resolve } from "path";
 import { readFileSync } from "fs";
@@ -22,7 +24,6 @@ import type {
     Funding,
     Approve,
     Cancel,
-    ProvideEscrowData,
 } from "@/benchmarks/contracts/output/escrow_Escrow";
 
 import benchmarkResults from "@/benchmarks/escrow/results_gas.json";
@@ -38,7 +39,21 @@ const loadFunCEscrowBoc = () => {
     return { bocEscrow };
 };
 
-describe("Escrow Gas Tests", () => {
+function testEscrow(
+    benchmarkResults: BenchmarkResult,
+    codeSizeResults: CodeSizeResult,
+    fromInit: (
+        id: bigint,
+        sellerAddress: Address,
+        guarantorAddress: Address,
+        buyerAddress: Address | null,
+        dealAmount: bigint,
+        guarantorRoyaltyPercent: bigint,
+        isFunded: boolean,
+        assetAddress: Address | null,
+        jettonWalletCode: Cell | null,
+    ) => Promise<Escrow>,
+) {
     let blockchain: Blockchain;
     let deployer: SandboxContract<TreasuryContract>;
     let seller: SandboxContract<TreasuryContract>;
@@ -46,77 +61,18 @@ describe("Escrow Gas Tests", () => {
     let guarantor: SandboxContract<TreasuryContract>;
     let lastCtxId = 1n;
 
-    let escrowContractFunC: SandboxContract<Escrow>;
-    let escrowContractTact: SandboxContract<Escrow>;
-
     const jettonWalletCode = beginCell().storeUint(0, 1).endCell();
 
     let step: Step;
-    const results = generateResults(benchmarkResults);
-    const codeSizeResults = generateCodeSizeResults(benchmarkCodeSizeResults);
-    const expectedCodeSize = codeSizeResults.at(-1)!;
-    const funcCodeSize = codeSizeResults.at(0)!;
-
-    const expectedResult = results.at(-1)!;
-    const funcResult = results.at(0)!;
 
     const dealAmount = toNano(1);
 
-    async function deployFuncContract(
-        assetAddress: Address | null,
-        dealAmount: bigint,
-        royalty: bigint,
-    ): Promise<SandboxContract<Escrow>> {
-        const escrowData = loadFunCEscrowBoc();
-        const escrowCell = Cell.fromBoc(escrowData.bocEscrow)[0]!;
-
-        const stateInitEscrowBuilder = beginCell()
-            .storeUint(lastCtxId++, 32)
-            .storeAddress(seller.address)
-            .storeAddress(guarantor.address)
-            .storeUint(dealAmount, 64)
-            .storeAddress(assetAddress);
-
-        const cell2 = beginCell()
-            .storeUint(royalty, 32)
-            .storeAddress(null)
-            .storeUint(0, 2)
-            .storeMaybeRef(assetAddress ? jettonWalletCode : null)
-            .endCell();
-
-        const stateInit = stateInitEscrowBuilder.storeRef(cell2).endCell();
-
-        const init = { code: escrowCell, data: stateInit };
-        const escrowAddress = contractAddress(0, init);
-
-        const escrowContract = blockchain.openContract(
-            await Escrow.fromAddress(escrowAddress),
-        );
-
-        const deployResult = await deployer.send({
-            to: escrowAddress,
-            value: toNano("0.1"),
-            init,
-            body: beginCell().endCell(),
-            sendMode: SendMode.PAY_GAS_SEPARATELY,
-        });
-
-        expect(deployResult.transactions).toHaveTransaction({
-            from: deployer.address,
-            to: escrowAddress,
-            success: true,
-            deploy: true,
-        });
-
-        return escrowContract;
-    }
-
-    async function deployTactContract(
+    async function deployContract(
         assetAddress: Address | null,
         dealAmount: bigint,
         royalty: bigint,
     ): Promise<SandboxContract<Escrow>> {
-        const contractInit = await Escrow.fromInit(
+        const contractInit = await fromInit(
             lastCtxId++,
             seller.address,
             guarantor.address,
@@ -135,7 +91,7 @@ describe("Escrow Gas Tests", () => {
             { value: toNano("0.1") },
             {
                 $$type: "ProvideEscrowData",
-            } as ProvideEscrowData,
+            },
         );
 
         expect(deployResult.transactions).toHaveTransaction({
@@ -161,18 +117,6 @@ describe("Escrow Gas Tests", () => {
         });
     });
 
-    afterAll(() => {
-        printBenchmarkTable(results, codeSizeResults, {
-            implementationName: "FunC",
-            printMode: "full",
-        });
-    });
-
-    beforeEach(async () => {
-        escrowContractFunC = await deployFuncContract(null, dealAmount, 1n);
-        escrowContractTact = await deployTactContract(null, dealAmount, 1n);
-    });
-
     const sendFunding = async (
         escrowContract: SandboxContract<Escrow>,
         from: Sender,
@@ -185,198 +129,200 @@ describe("Escrow Gas Tests", () => {
         return await escrowContract.send(from, { value }, msg);
     };
 
-    it("fundingTon", async () => {
-        const runFundingTest = async (
-            escrowContract: SandboxContract<Escrow>,
-        ) => {
-            const sendResult = await step("fundingTon", async () =>
-                sendFunding(escrowContract, buyer.getSender(), toNano(1)),
-            );
-            expect(sendResult.transactions).not.toHaveTransaction({
-                success: false,
-            });
-            return getUsedGas(sendResult, "internal");
+    const sendChangeCode = async (
+        escrowContract: SandboxContract<Escrow>,
+        from: Sender,
+        value: bigint,
+        newCode: Cell,
+    ) => {
+        const msg: UpdateJettonWalletCode = {
+            $$type: "UpdateJettonWalletCode",
+            newJettonWalletCode: newCode,
         };
 
-        const fundingGasUsedFunC = await runFundingTest(escrowContractFunC);
-        const fundingGasUsedTact = await runFundingTest(escrowContractTact);
-
-        expect(fundingGasUsedFunC).toEqual(funcResult.gas["fundingTon"]);
-        expect(fundingGasUsedTact).toEqual(expectedResult.gas["fundingTon"]);
-    });
+        return await escrowContract.send(from, { value }, msg);
+    };
 
-    it("changeCode", async () => {
-        const sendChangeCode = async (
-            escrowContract: SandboxContract<Escrow>,
-            from: Sender,
-            value: bigint,
-            newCode: Cell,
-        ) => {
-            const msg: UpdateJettonWalletCode = {
-                $$type: "UpdateJettonWalletCode",
-                newJettonWalletCode: newCode,
-            };
-
-            return await escrowContract.send(from, { value }, msg);
+    const sendApprove = async (
+        escrowContract: SandboxContract<Escrow>,
+        from: Sender,
+        value: bigint,
+    ) => {
+        const msg: Approve = {
+            $$type: "Approve",
         };
 
-        const runChangeCodeTest = async (
-            escrowContract: SandboxContract<Escrow>,
-        ) => {
-            const newCode = beginCell().endCell();
-
-            const sendResult = await step("changeCode", async () =>
-                sendChangeCode(
-                    escrowContract,
-                    seller.getSender(),
-                    toNano("0.05"),
-                    newCode,
-                ),
-            );
-            expect(sendResult.transactions).not.toHaveTransaction({
-                success: false,
-            });
-            return getUsedGas(sendResult, "internal");
+        return await escrowContract.send(from, { value }, msg);
+    };
+
+    const sendCancel = async (
+        escrowContract: SandboxContract<Escrow>,
+        from: Sender,
+        value: bigint,
+    ) => {
+        const msg: Cancel = {
+            $$type: "Cancel",
         };
 
-        escrowContractFunC = await deployFuncContract(
-            guarantor.address,
-            dealAmount,
-            1n,
+        return await escrowContract.send(from, { value }, msg);
+    };
+
+    it("fundingTon", async () => {
+        const contract = await deployContract(null, dealAmount, 1n);
+
+        const sendResult = await step("fundingTon", async () =>
+            sendFunding(contract, buyer.getSender(), toNano(1)),
         );
 
-        escrowContractTact = await deployTactContract(
+        expect(sendResult.transactions).not.toHaveTransaction({
+            success: false,
+        });
+
+        const gasUsed = getUsedGas(sendResult, "internal");
+        expect(gasUsed).toEqual(benchmarkResults.gas["fundingTon"]);
+    });
+
+    it("changeCode", async () => {
+        const contract = await deployContract(
             guarantor.address,
             dealAmount,
             1n,
         );
+        const newCode = beginCell().endCell();
+
+        const sendResult = await step("changeCode", async () =>
+            sendChangeCode(
+                contract,
+                seller.getSender(),
+                toNano("0.05"),
+                newCode,
+            ),
+        );
 
-        const changeCodeGasUsedFunC =
-            await runChangeCodeTest(escrowContractFunC);
-        const changeCodeGasUsedTact =
-            await runChangeCodeTest(escrowContractTact);
+        expect(sendResult.transactions).not.toHaveTransaction({
+            success: false,
+        });
 
-        expect(changeCodeGasUsedFunC).toEqual(funcResult.gas["changeCode"]);
-        expect(changeCodeGasUsedTact).toEqual(expectedResult.gas["changeCode"]);
+        const gasUsed = getUsedGas(sendResult, "internal");
+        expect(gasUsed).toEqual(benchmarkResults.gas["changeCode"]);
     });
 
     it("approveTon", async () => {
-        const sendApprove = async (
-            escrowContract: SandboxContract<Escrow>,
-            from: Sender,
-            value: bigint,
-        ) => {
-            const msg: Approve = {
-                $$type: "Approve",
-            };
-
-            return await escrowContract.send(from, { value }, msg);
-        };
+        const contract = await deployContract(null, dealAmount, 1n);
 
-        const runApproveTest = async (
-            escrowContract: SandboxContract<Escrow>,
-        ) => {
-            await escrowContract.send(buyer.getSender(), { value: toNano(1) }, {
+        await contract.send(
+            buyer.getSender(),
+            { value: toNano(1) },
+            {
                 $$type: "Funding",
-            } as Funding);
-
-            const sendResult = await step("approveTon", async () =>
-                sendApprove(
-                    escrowContract,
-                    guarantor.getSender(),
-                    toNano("0.05"),
-                ),
-            );
-            expect(sendResult.transactions).not.toHaveTransaction({
-                success: false,
-            });
-            return getUsedGas(sendResult, "internal");
-        };
+            },
+        );
+
+        const sendResult = await step("approveTon", async () =>
+            sendApprove(contract, guarantor.getSender(), toNano("0.05")),
+        );
 
-        const approveGasUsedFunC = await runApproveTest(escrowContractFunC);
-        const approveGasUsedTact = await runApproveTest(escrowContractTact);
+        expect(sendResult.transactions).not.toHaveTransaction({
+            success: false,
+        });
 
-        expect(approveGasUsedFunC).toEqual(funcResult.gas["approveTon"]);
-        expect(approveGasUsedTact).toEqual(expectedResult.gas["approveTon"]);
+        const gasUsed = getUsedGas(sendResult, "internal");
+        expect(gasUsed).toEqual(benchmarkResults.gas["approveTon"]);
     });
 
     it("cancelTon", async () => {
-        const sendCancel = async (
-            escrowContract: SandboxContract<Escrow>,
-            from: Sender,
-            value: bigint,
-        ) => {
-            const msg: Cancel = {
-                $$type: "Cancel",
-            };
-
-            return await escrowContract.send(from, { value }, msg);
-        };
+        const contract = await deployContract(null, dealAmount, 1n);
 
-        const runCancelTest = async (
-            escrowContract: SandboxContract<Escrow>,
-        ) => {
-            await escrowContract.send(buyer.getSender(), { value: toNano(1) }, {
+        await contract.send(
+            buyer.getSender(),
+            { value: toNano(1) },
+            {
                 $$type: "Funding",
-            } as Funding);
-
-            const sendResult = await step("cancelTon", async () =>
-                sendCancel(
-                    escrowContract,
-                    guarantor.getSender(),
-                    toNano("0.05"),
-                ),
-            );
-            expect(sendResult.transactions).not.toHaveTransaction({
-                success: false,
-            });
-            return getUsedGas(sendResult, "internal");
-        };
+            },
+        );
 
-        const cancelGasUsedFunC = await runCancelTest(escrowContractFunC);
-        const cancelGasUsedTact = await runCancelTest(escrowContractTact);
+        const sendResult = await step("cancelTon", async () =>
+            sendCancel(contract, guarantor.getSender(), toNano("0.05")),
+        );
+
+        expect(sendResult.transactions).not.toHaveTransaction({
+            success: false,
+        });
 
-        expect(cancelGasUsedFunC).toEqual(funcResult.gas["cancelTon"]);
-        expect(cancelGasUsedTact).toEqual(expectedResult.gas["cancelTon"]);
+        const gasUsed = getUsedGas(sendResult, "internal");
+        expect(gasUsed).toEqual(benchmarkResults.gas["cancelTon"]);
     });
 
     it("cells", async () => {
-        expect(
-            (
-                await getStateSizeForAccount(
-                    blockchain,
-                    escrowContractFunC.address,
-                )
-            ).cells,
-        ).toEqual(funcCodeSize.size["cells"]);
+        const contract = await deployContract(null, dealAmount, 1n);
 
         expect(
-            (
-                await getStateSizeForAccount(
-                    blockchain,
-                    escrowContractTact.address,
-                )
-            ).cells,
-        ).toEqual(expectedCodeSize.size["cells"]);
+            (await getStateSizeForAccount(blockchain, contract.address)).cells,
+        ).toEqual(codeSizeResults.size["cells"]);
     });
 
     it("bits", async () => {
+        const contract = await deployContract(null, dealAmount, 1n);
         expect(
-            (
-                await getStateSizeForAccount(
-                    blockchain,
-                    escrowContractFunC.address,
-                )
-            ).bits,
-        ).toEqual(funcCodeSize.size["bits"]);
+            (await getStateSizeForAccount(blockchain, contract.address)).bits,
+        ).toEqual(codeSizeResults.size["bits"]);
+    });
+}
 
-        expect(
-            (
-                await getStateSizeForAccount(
-                    blockchain,
-                    escrowContractTact.address,
-                )
-            ).bits,
-        ).toEqual(expectedCodeSize.size["bits"]);
+describe("Escrow Gas Tests", () => {
+    const fullResults = generateResults(benchmarkResults);
+    const fullCodeSizeResults = generateCodeSizeResults(
+        benchmarkCodeSizeResults,
+    );
+
+    describe("func", () => {
+        const funcCodeSize = fullCodeSizeResults.at(0)!;
+        const funcResult = fullResults.at(0)!;
+
+        function fromInit(
+            id: bigint,
+            sellerAddress: Address,
+            guarantorAddress: Address,
+            buyerAddress: Address | null,
+            dealAmount: bigint,
+            guarantorRoyaltyPercent: bigint,
+            isFunded: boolean,
+            assetAddress: Address | null,
+            jettonWalletCode: Cell | null,
+        ) {
+            const __code = Cell.fromBoc(loadFunCEscrowBoc().bocEscrow)[0]!;
+            const cell1 = beginCell()
+                .storeUint(id, 32)
+                .storeAddress(sellerAddress)
+                .storeAddress(guarantorAddress)
+                .storeUint(dealAmount, 64)
+                .storeAddress(assetAddress);
+
+            const cell2 = beginCell()
+                .storeUint(guarantorRoyaltyPercent, 32)
+                .storeAddress(buyerAddress)
+                .storeUint(isFunded ? 1 : 0, 2)
+                .storeMaybeRef(assetAddress ? jettonWalletCode : null)
+                .endCell();
+            const __data = cell1.storeRef(cell2).endCell();
+            const __gen_init = { code: __code, data: __data };
+            const address = contractAddress(0, __gen_init);
+            return Promise.resolve(new Escrow(address, __gen_init));
+        }
+
+        testEscrow(funcResult, funcCodeSize, fromInit);
+    });
+
+    describe("tact", () => {
+        const tactCodeSize = fullCodeSizeResults.at(-1)!;
+        const tactResult = fullResults.at(-1)!;
+        testEscrow(tactResult, tactCodeSize, Escrow.fromInit.bind(Escrow));
+    });
+
+    afterAll(() => {
+        printBenchmarkTable(fullResults, fullCodeSizeResults, {
+            implementationName: "FunC",
+            printMode: "full",
+        });
     });
 });
diff --git a/src/benchmarks/utils/gas.ts b/src/benchmarks/utils/gas.ts
index b6b1e08c..7153a5c7 100644
--- a/src/benchmarks/utils/gas.ts
+++ b/src/benchmarks/utils/gas.ts
@@ -38,7 +38,7 @@ function getUsedGasInternal(sendResult: SendMessageResult): number {
         .reduceRight((prev, cur) => prev + cur);
 }
 
-type BenchmarkResult = {
+export type BenchmarkResult = {
     label: string;
     pr: string | undefined;
     gas: Record<string, number>;
@@ -75,7 +75,7 @@ export type RawCodeSizeResult = {
     }[];
 };
 
-type CodeSizeResult = {
+export type CodeSizeResult = {
     label: string;
     pr: string | undefined;
     size: Record<string, number>;
diff --git a/src/bindings/typescript/serializers.ts b/src/bindings/typescript/serializers.ts
index 801213fb..e008cb87 100644
--- a/src/bindings/typescript/serializers.ts
+++ b/src/bindings/typescript/serializers.ts
@@ -686,10 +686,10 @@ const struct: Serializer<{ name: string; optional: boolean }> = {
     },
 };
 
-type MapSerializerDescrKey =
+export type MapSerializerDescrKey =
     | { kind: "int" | "uint"; bits: number }
     | { kind: "address" };
-type MapSerializerDescrValue =
+export type MapSerializerDescrValue =
     | { kind: "int" | "uint"; bits: number }
     | { kind: "varuint"; length: number }
     | { kind: "varint"; length: number }
@@ -701,7 +701,7 @@ type MapSerializerDescr = {
     key: MapSerializerDescrKey;
     value: MapSerializerDescrValue;
 };
-function getKeyParser(src: MapSerializerDescrKey) {
+export function getKeyParser(src: MapSerializerDescrKey) {
     switch (src.kind) {
         case "int": {
             if (src.bits <= 32) {
@@ -722,7 +722,7 @@ function getKeyParser(src: MapSerializerDescrKey) {
         }
     }
 }
-function getValueParser(src: MapSerializerDescrValue) {
+export function getValueParser(src: MapSerializerDescrValue) {
     switch (src.kind) {
         case "int": {
             if (src.bits <= 32) {
@@ -759,7 +759,7 @@ function getValueParser(src: MapSerializerDescrValue) {
     }
 }
 
-const map: Serializer<MapSerializerDescr> = {
+export const mapSerializers: Serializer<MapSerializerDescr> = {
     abiMatcher(src) {
         if (src.kind === "dict") {
             if (src.format !== null && src.format !== undefined) {
@@ -966,5 +966,5 @@ export const serializers: Serializer<any>[] = [
 
     // Structs as fallback
     struct,
-    map,
+    mapSerializers,
 ];
diff --git a/src/fmt/cst/cst-helpers.ts b/src/fmt/cst/cst-helpers.ts
index b0cdfa71..c422221f 100644
--- a/src/fmt/cst/cst-helpers.ts
+++ b/src/fmt/cst/cst-helpers.ts
@@ -82,6 +82,16 @@ export const childByField = (node: Cst, field: string): undefined | CstNode => {
     return undefined;
 };
 
+export const childrenByField = (node: Cst, field: string): CstNode[] => {
+    if (node.$ === "leaf") {
+        return [];
+    }
+
+    return node.children
+        .filter((c) => c.$ === "node")
+        .filter((c) => c.field === field);
+};
+
 export const childIdxByField = (node: Cst, field: string): number => {
     if (node.$ === "leaf") {
         return -1;
diff --git a/src/fmt/cst/cst-parser.ts b/src/fmt/cst/cst-parser.ts
index 8c0163e9..6a6c564d 100644
--- a/src/fmt/cst/cst-parser.ts
+++ b/src/fmt/cst/cst-parser.ts
@@ -32,7 +32,7 @@ export type CstNode = {
     readonly type: string;
     readonly group: string;
     readonly field: string;
-    children: Cst[];
+    readonly children: readonly Cst[];
 };
 
 export const CstLeaf = (text: string): CstLeaf => ({
@@ -49,7 +49,7 @@ export const CstNode = (
 ): CstNode => {
     if (
         children.length === 1 &&
-        children[0].$ === "node" &&
+        children[0]?.$ === "node" &&
         children[0].type === ""
     ) {
         return CstNode(children[0].children, type, field, group);
@@ -100,7 +100,7 @@ const consumeClass = (
     cond: (c: string) => boolean,
 ): boolean => {
     if (ctx.p === ctx.l) return false;
-    const c = ctx.s[ctx.p];
+    const c = ctx.s[ctx.p] ?? "";
     if (!cond(c)) return false;
     ctx.p++;
     const b2: Builder = [];
@@ -130,7 +130,7 @@ export const consumeAny = (ctx: Context, b: Builder) => {
         return false;
     }
 
-    const c = ctx.s[ctx.p];
+    const c = ctx.s[ctx.p] ?? "";
     b.push(CstLeaf(c));
     ctx.p++;
     return true;
@@ -208,6 +208,7 @@ export const contractItemDecl: Rule = (ctx, b, field) => {
     let r = ContractInit(ctx, b2);
     r = r || ((ctx.p = p), Receiver(ctx, b2));
     r = r || ((ctx.p = p), $Function(ctx, b2));
+    r = r || ((ctx.p = p), AsmFunction(ctx, b2));
     r = r || ((ctx.p = p), Constant(ctx, b2));
     r = r || ((ctx.p = p), storageVar(ctx, b2));
     pushGroupTo(b, b2, "contractItemDecl");
@@ -218,6 +219,7 @@ export const traitItemDecl: Rule = (ctx, b, field) => {
     const p = ctx.p;
     let r = Receiver(ctx, b2);
     r = r || ((ctx.p = p), $Function(ctx, b2));
+    r = r || ((ctx.p = p), AsmFunction(ctx, b2));
     r = r || ((ctx.p = p), Constant(ctx, b2));
     r = r || ((ctx.p = p), storageVar(ctx, b2));
     pushGroupTo(b, b2, "traitItemDecl");
@@ -741,6 +743,25 @@ export const TypeId: Rule = (ctx, b, field) => {
     }
     return r;
 };
+export const typeArgs: Rule = (ctx, b, field) => {
+    return generic($type)(ctx, b, field);
+};
+export const generic: (T: Rule) => Rule = (T) => {
+    return (ctx, b, field) => {
+        const b2: Builder = [];
+        const p = ctx.p;
+        let r = consumeString(ctx, b2, "<");
+        r = r && generic_optional_53(T)(ctx, b2);
+        r = r && consumeString(ctx, b2, ">");
+        if (r && b2.length > 0) {
+            b.push(CstNode(b2, field ?? "", field ?? ""));
+        }
+        if (!r) {
+            ctx.p = p;
+        }
+        return r;
+    };
+};
 export const statement: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
@@ -763,7 +784,7 @@ export const statements: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "{");
-    r = r && statements_star_53(ctx, b2);
+    r = r && statements_star_54(ctx, b2);
     r = r && consumeString(ctx, b2, "}");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -778,7 +799,7 @@ export const StatementLet: Rule = (ctx, b, field) => {
     const p = ctx.p;
     let r = keyword((ctx, b) => consumeString(ctx, b, "let"))(ctx, b2);
     r = r && Id(ctx, b2, "name");
-    r = r && StatementLet_optional_54(ctx, b2, "type");
+    r = r && StatementLet_optional_55(ctx, b2, "type");
     r = r && consumeString(ctx, b2, "=");
     r = r && expression(ctx, b2, "init");
     r = r && semicolon(ctx, b2);
@@ -832,7 +853,7 @@ export const StatementReturn: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = keyword((ctx, b) => consumeString(ctx, b, "return"))(ctx, b2);
-    r = r && StatementReturn_optional_55(ctx, b2, "expression");
+    r = r && StatementReturn_optional_56(ctx, b2, "expression");
     r = r && semicolon(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "StatementReturn", field ?? ""));
@@ -859,8 +880,7 @@ export const StatementAssign: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = expression(ctx, b2, "left");
-    r = r && StatementAssign_optional_56(ctx, b2, "operator");
-    r = r && consumeString(ctx, b2, "=");
+    r = r && StatementAssign_alt_57(ctx, b2, "operator");
     r = r && expression(ctx, b2, "right");
     r = r && semicolon(ctx, b2);
     if (r && b2.length > 0) {
@@ -877,7 +897,7 @@ export const StatementCondition: Rule = (ctx, b, field) => {
     let r = keyword((ctx, b) => consumeString(ctx, b, "if"))(ctx, b2);
     r = r && expression(ctx, b2, "condition");
     r = r && statements(ctx, b2, "trueBranch");
-    r = r && StatementCondition_optional_59(ctx, b2, "falseBranch");
+    r = r && StatementCondition_optional_60(ctx, b2, "falseBranch");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "StatementCondition", field ?? ""));
     }
@@ -935,7 +955,7 @@ export const StatementTry: Rule = (ctx, b, field) => {
     const p = ctx.p;
     let r = keyword((ctx, b) => consumeString(ctx, b, "try"))(ctx, b2);
     r = r && statements(ctx, b2, "body");
-    r = r && StatementTry_optional_61(ctx, b2, "handler");
+    r = r && StatementTry_optional_62(ctx, b2, "handler");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "StatementTry", field ?? ""));
     }
@@ -967,26 +987,18 @@ export const StatementForEach: Rule = (ctx, b, field) => {
 export const augmentedOp: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = consumeString(ctx, b2, "||");
-    r = r || ((ctx.p = p), consumeString(ctx, b2, "&&"));
-    r = r || ((ctx.p = p), consumeString(ctx, b2, ">>"));
-    r = r || ((ctx.p = p), consumeString(ctx, b2, "<<"));
-    r =
-        r ||
-        ((ctx.p = p),
-        consumeClass(
-            ctx,
-            b2,
-            (c) =>
-                c === "-" ||
-                c === "+" ||
-                c === "*" ||
-                c === "/" ||
-                c === "%" ||
-                c === "|" ||
-                c === "&" ||
-                c === "^",
-        ));
+    let r = consumeString(ctx, b2, "||=");
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "&&="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, ">>="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "<<="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "-="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "+="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "*="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "/="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "%="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "|="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "&="));
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "^="));
     pushGroupTo(b, b2, "augmentedOp");
     return r;
 };
@@ -1006,7 +1018,7 @@ export const semicolon: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, ";");
-    r = r || ((ctx.p = p), semicolon_lookpos_62(ctx, b2));
+    r = r || ((ctx.p = p), semicolon_lookpos_63(ctx, b2));
     pushGroupTo(b, b2, "semicolon");
     return r;
 };
@@ -1047,7 +1059,7 @@ export const PunnedField: Rule = (ctx, b, field) => {
 export const optionalRest: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = optionalRest_seq_63(ctx, b2);
+    let r = optionalRest_seq_64(ctx, b2);
     r = r || ((ctx.p = p), NoRestArgument(ctx, b2));
     pushGroupTo(b, b2, "optionalRest");
     return r;
@@ -1081,7 +1093,7 @@ export const Conditional: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = or(ctx, b2, "head");
-    r = r && Conditional_optional_65(ctx, b2, "tail");
+    r = r && Conditional_optional_66(ctx, b2, "tail");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "Conditional", field ?? ""));
     }
@@ -1122,16 +1134,16 @@ export const bitwiseAnd: Rule = (ctx, b, field) => {
     );
 };
 export const equality: Rule = (ctx, b, field) => {
-    return Binary(compare, equality_alt_66)(ctx, b, field);
+    return Binary(compare, equality_alt_67)(ctx, b, field);
 };
 export const compare: Rule = (ctx, b, field) => {
-    return Binary(bitwiseShift, compare_alt_67)(ctx, b, field);
+    return Binary(bitwiseShift, compare_alt_68)(ctx, b, field);
 };
 export const bitwiseShift: Rule = (ctx, b, field) => {
-    return Binary(add, bitwiseShift_alt_68)(ctx, b, field);
+    return Binary(add, bitwiseShift_alt_69)(ctx, b, field);
 };
 export const add: Rule = (ctx, b, field) => {
-    return Binary(mul, add_alt_69)(ctx, b, field);
+    return Binary(mul, add_alt_70)(ctx, b, field);
 };
 export const mul: Rule = (ctx, b, field) => {
     return Binary(Unary, (ctx, b) =>
@@ -1141,7 +1153,7 @@ export const mul: Rule = (ctx, b, field) => {
 export const Unary: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = Unary_star_70(ctx, b2, "prefixes");
+    let r = Unary_star_71(ctx, b2, "prefixes");
     r = r && Suffix(ctx, b2, "expression");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "Unary", field ?? ""));
@@ -1155,7 +1167,7 @@ export const Suffix: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = primary(ctx, b2, "expression");
-    r = r && Suffix_star_71(ctx, b2, "suffixes");
+    r = r && Suffix_star_72(ctx, b2, "suffixes");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "Suffix", field ?? ""));
     }
@@ -1242,6 +1254,8 @@ export const primary: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = Parens(ctx, b2);
+    r = r || ((ctx.p = p), MapLiteral(ctx, b2));
+    r = r || ((ctx.p = p), SetLiteral(ctx, b2));
     r = r || ((ctx.p = p), StructInstance(ctx, b2));
     r = r || ((ctx.p = p), IntegerLiteral(ctx, b2));
     r = r || ((ctx.p = p), BoolLiteral(ctx, b2));
@@ -1304,7 +1318,7 @@ export const StructInstanceFields: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "{");
-    r = r && StructInstanceFields_optional_72(ctx, b2, "fields");
+    r = r && StructInstanceFields_optional_73(ctx, b2, "fields");
     r = r && consumeString(ctx, b2, "}");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "StructInstanceFields", field ?? ""));
@@ -1314,6 +1328,52 @@ export const StructInstanceFields: Rule = (ctx, b, field) => {
     }
     return r;
 };
+export const MapLiteral: Rule = (ctx, b, field) => {
+    const b2: Builder = [];
+    const p = ctx.p;
+    let r = keyword((ctx, b) => consumeString(ctx, b, "map"))(ctx, b2);
+    r = r && typeArgs(ctx, b2, "typeArgs");
+    r = r && consumeString(ctx, b2, "{");
+    r = r && MapLiteral_optional_74(ctx, b2, "fields");
+    r = r && consumeString(ctx, b2, "}");
+    if (r && b2.length > 0) {
+        b.push(CstNode(b2, "MapLiteral", field ?? ""));
+    }
+    if (!r) {
+        ctx.p = p;
+    }
+    return r;
+};
+export const mapField: Rule = (ctx, b, field) => {
+    const b2: Builder = [];
+    const p = ctx.p;
+    let r = expression(ctx, b2, "key");
+    r = r && consumeString(ctx, b2, ":");
+    r = r && expression(ctx, b2, "value");
+    if (r && b2.length > 0) {
+        b.push(CstNode(b2, field ?? "", field ?? ""));
+    }
+    if (!r) {
+        ctx.p = p;
+    }
+    return r;
+};
+export const SetLiteral: Rule = (ctx, b, field) => {
+    const b2: Builder = [];
+    const p = ctx.p;
+    let r = consumeString(ctx, b2, "set");
+    r = r && typeArgs(ctx, b2, "typeArgs");
+    r = r && consumeString(ctx, b2, "{");
+    r = r && SetLiteral_optional_75(ctx, b2, "fields");
+    r = r && consumeString(ctx, b2, "}");
+    if (r && b2.length > 0) {
+        b.push(CstNode(b2, "SetLiteral", field ?? ""));
+    }
+    if (!r) {
+        ctx.p = p;
+    }
+    return r;
+};
 export const InitOf: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
@@ -1345,7 +1405,7 @@ export const StructFieldInitializer: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = Id(ctx, b2, "name");
-    r = r && StructFieldInitializer_optional_74(ctx, b2, "init");
+    r = r && StructFieldInitializer_optional_77(ctx, b2, "init");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "StructFieldInitializer", field ?? ""));
     }
@@ -1359,7 +1419,7 @@ export const ParameterList: (T: Rule) => Rule = (T) => {
         const b2: Builder = [];
         const p = ctx.p;
         let r = consumeString(ctx, b2, "(");
-        r = r && ParameterList_optional_75(T)(ctx, b2);
+        r = r && ParameterList_optional_78(T)(ctx, b2);
         r = r && consumeString(ctx, b2, ")");
         if (r && b2.length > 0) {
             b.push(CstNode(b2, "ParameterList", field ?? ""));
@@ -1388,7 +1448,7 @@ export const commaList: (T: Rule) => Rule = (T) => {
         const b2: Builder = [];
         const p = ctx.p;
         let r = inter(T, (ctx, b) => consumeString(ctx, b, ","))(ctx, b2);
-        r = r && commaList_optional_76(T)(ctx, b2);
+        r = r && commaList_optional_79(T)(ctx, b2);
         if (r && b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
         }
@@ -1401,7 +1461,7 @@ export const commaList: (T: Rule) => Rule = (T) => {
 export const IntegerLiteral: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = IntegerLiteral_alt_77(ctx, b2, "value");
+    let r = IntegerLiteral_alt_80(ctx, b2, "value");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "IntegerLiteral", field ?? ""));
     }
@@ -1413,7 +1473,7 @@ export const IntegerLiteral: Rule = (ctx, b, field) => {
 export const IntegerLiteralDec: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = IntegerLiteralDec_lex_78(ctx, b2, "digits");
+    let r = IntegerLiteralDec_lex_81(ctx, b2, "digits");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "IntegerLiteralDec", field ?? ""));
     }
@@ -1425,7 +1485,7 @@ export const IntegerLiteralDec: Rule = (ctx, b, field) => {
 export const IntegerLiteralHex: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = IntegerLiteralHex_lex_80(ctx, b2, "digits");
+    let r = IntegerLiteralHex_lex_83(ctx, b2, "digits");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "IntegerLiteralHex", field ?? ""));
     }
@@ -1437,7 +1497,7 @@ export const IntegerLiteralHex: Rule = (ctx, b, field) => {
 export const IntegerLiteralBin: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = IntegerLiteralBin_lex_82(ctx, b2, "digits");
+    let r = IntegerLiteralBin_lex_85(ctx, b2, "digits");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "IntegerLiteralBin", field ?? ""));
     }
@@ -1449,7 +1509,7 @@ export const IntegerLiteralBin: Rule = (ctx, b, field) => {
 export const IntegerLiteralOct: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = IntegerLiteralOct_lex_84(ctx, b2, "digits");
+    let r = IntegerLiteralOct_lex_87(ctx, b2, "digits");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "IntegerLiteralOct", field ?? ""));
     }
@@ -1461,7 +1521,7 @@ export const IntegerLiteralOct: Rule = (ctx, b, field) => {
 export const underscored: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const p = ctx.p;
-        const r = underscored_seq_88(T)(ctx, []);
+        const r = underscored_seq_91(T)(ctx, []);
         if (r) {
             const text = ctx.s.substring(p, ctx.p);
             b.push(CstLeaf(text));
@@ -1486,7 +1546,7 @@ export const idPart: Rule = (ctx, b, field) => {
 export const Id: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = Id_lex_93(ctx, b2, "name");
+    let r = Id_lex_96(ctx, b2, "name");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "Id", field ?? ""));
     }
@@ -1498,8 +1558,8 @@ export const Id: Rule = (ctx, b, field) => {
 export const FuncId: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = FuncId_optional_94(ctx, b2, "accessor");
-    r = r && FuncId_stringify_99(ctx, b2, "id");
+    let r = FuncId_optional_97(ctx, b2, "accessor");
+    r = r && FuncId_stringify_102(ctx, b2, "id");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "FuncId", field ?? ""));
     }
@@ -1511,8 +1571,8 @@ export const FuncId: Rule = (ctx, b, field) => {
 export const BoolLiteral: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = BoolLiteral_alt_100(ctx, b2, "value");
-    r = r && BoolLiteral_lookneg_101(ctx, b2);
+    let r = BoolLiteral_alt_103(ctx, b2, "value");
+    r = r && BoolLiteral_lookneg_104(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "BoolLiteral", field ?? ""));
     }
@@ -1524,7 +1584,7 @@ export const BoolLiteral: Rule = (ctx, b, field) => {
 export const StringLiteral: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = StringLiteral_lex_107(ctx, b2, "value");
+    let r = StringLiteral_lex_110(ctx, b2, "value");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "StringLiteral", field ?? ""));
     }
@@ -1549,9 +1609,9 @@ export const escapeChar: Rule = (ctx, b, field) => {
             c === "b" ||
             c === "f",
     );
-    r = r || ((ctx.p = p), escapeChar_seq_115(ctx, b2));
     r = r || ((ctx.p = p), escapeChar_seq_118(ctx, b2));
     r = r || ((ctx.p = p), escapeChar_seq_121(ctx, b2));
+    r = r || ((ctx.p = p), escapeChar_seq_124(ctx, b2));
     pushGroupTo(b, b2, "escapeChar");
     return r;
 };
@@ -1572,7 +1632,7 @@ export const keyword: (T: Rule) => Rule = (T) => {
             ...ctx,
             space: undefined,
         };
-        const r = keyword_seq_123(T)(newCtx, b2);
+        const r = keyword_seq_126(T)(newCtx, b2);
         if (r) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
         }
@@ -1582,14 +1642,14 @@ export const keyword: (T: Rule) => Rule = (T) => {
     };
 };
 export const reservedWord: Rule = (ctx, b, field) => {
-    return keyword(reservedWord_alt_124)(ctx, b, field);
+    return keyword(reservedWord_alt_127)(ctx, b, field);
 };
 export const space: Rule = (ctx, b, field) => {
     const b2: Builder = [];
-    const r = space_alt_128(ctx, b2);
+    const r = space_alt_131(ctx, b2);
     if (r) {
         let p = ctx.p;
-        while (((p = ctx.p), space_alt_128(ctx, b2))) {}
+        while (((p = ctx.p), space_alt_131(ctx, b2))) {}
         ctx.p = p;
     }
     if (b2.length > 0) {
@@ -1611,7 +1671,7 @@ export const multiLineComment: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "/*");
-    r = r && multiLineComment_stringify_132(ctx, b2);
+    r = r && multiLineComment_stringify_135(ctx, b2);
     r = r && consumeString(ctx, b2, "*/");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -1625,7 +1685,7 @@ export const singleLineComment: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "//");
-    r = r && singleLineComment_stringify_134(ctx, b2);
+    r = r && singleLineComment_stringify_137(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -1637,8 +1697,8 @@ export const singleLineComment: Rule = (ctx, b, field) => {
 export const JustImports: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = JustImports_star_135(ctx, b2, "imports");
-    r = r && JustImports_star_136(ctx, b2);
+    let r = JustImports_star_138(ctx, b2, "imports");
+    r = r && JustImports_star_139(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, "JustImports", field ?? ""));
     }
@@ -1652,7 +1712,7 @@ export const inter: (A: Rule, B: Rule) => Rule = (A, B) => {
         const b2: Builder = [];
         const p = ctx.p;
         let r = A(ctx, b2, "head");
-        r = r && inter_star_138(A, B)(ctx, b2, "tail");
+        r = r && inter_star_141(A, B)(ctx, b2, "tail");
         if (r && b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
         }
@@ -2281,7 +2341,19 @@ export const TypeId_lex_52: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const statements_star_53: Rule = (ctx, b, field) => {
+export const generic_optional_53: (T: Rule) => Rule = (T) => {
+    return (ctx, b, field) => {
+        const b2: Builder = [];
+        const p = ctx.p;
+        let r = commaList(T)(ctx, b2);
+        r = r || ((ctx.p = p), true);
+        if (r && b2.length > 0) {
+            b.push(CstNode(b2, field ?? "", field ?? ""));
+        }
+        return r;
+    };
+};
+export const statements_star_54: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (((p = ctx.p), statement(ctx, b2))) {}
@@ -2291,7 +2363,7 @@ export const statements_star_53: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const StatementLet_optional_54: Rule = (ctx, b, field) => {
+export const StatementLet_optional_55: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = ascription(ctx, b2);
@@ -2301,7 +2373,7 @@ export const StatementLet_optional_54: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StatementReturn_optional_55: Rule = (ctx, b, field) => {
+export const StatementReturn_optional_56: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = expression(ctx, b2);
@@ -2311,17 +2383,17 @@ export const StatementReturn_optional_55: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StatementAssign_optional_56: Rule = (ctx, b, field) => {
+export const StatementAssign_alt_57: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = augmentedOp(ctx, b2);
-    r = r || ((ctx.p = p), true);
-    if (r && b2.length > 0) {
+    r = r || ((ctx.p = p), consumeString(ctx, b2, "="));
+    if (b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const StatementCondition_alt_57: Rule = (ctx, b, field) => {
+export const StatementCondition_alt_58: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = FalseBranch(ctx, b2);
@@ -2331,11 +2403,11 @@ export const StatementCondition_alt_57: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StatementCondition_seq_58: Rule = (ctx, b, field) => {
+export const StatementCondition_seq_59: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = keyword((ctx, b) => consumeString(ctx, b, "else"))(ctx, b2);
-    r = r && StatementCondition_alt_57(ctx, b2);
+    r = r && StatementCondition_alt_58(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2344,17 +2416,17 @@ export const StatementCondition_seq_58: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StatementCondition_optional_59: Rule = (ctx, b, field) => {
+export const StatementCondition_optional_60: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = StatementCondition_seq_58(ctx, b2);
+    let r = StatementCondition_seq_59(ctx, b2);
     r = r || ((ctx.p = p), true);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const StatementTry_seq_60: Rule = (ctx, b, field) => {
+export const StatementTry_seq_61: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = keyword((ctx, b) => consumeString(ctx, b, "catch"))(ctx, b2);
@@ -2370,23 +2442,23 @@ export const StatementTry_seq_60: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StatementTry_optional_61: Rule = (ctx, b, field) => {
+export const StatementTry_optional_62: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = StatementTry_seq_60(ctx, b2);
+    let r = StatementTry_seq_61(ctx, b2);
     r = r || ((ctx.p = p), true);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const semicolon_lookpos_62: Rule = (ctx, b, field) => {
+export const semicolon_lookpos_63: Rule = (ctx, b, field) => {
     const p = ctx.p;
     const r = consumeString(ctx, [], "}");
     ctx.p = p;
     return r;
 };
-export const optionalRest_seq_63: Rule = (ctx, b, field) => {
+export const optionalRest_seq_64: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, ",");
@@ -2399,7 +2471,7 @@ export const optionalRest_seq_63: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const Conditional_seq_64: Rule = (ctx, b, field) => {
+export const Conditional_seq_65: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "?");
@@ -2414,17 +2486,17 @@ export const Conditional_seq_64: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const Conditional_optional_65: Rule = (ctx, b, field) => {
+export const Conditional_optional_66: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = Conditional_seq_64(ctx, b2);
+    let r = Conditional_seq_65(ctx, b2);
     r = r || ((ctx.p = p), true);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const equality_alt_66: Rule = (ctx, b, field) => {
+export const equality_alt_67: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "!=");
@@ -2434,7 +2506,7 @@ export const equality_alt_66: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const compare_alt_67: Rule = (ctx, b, field) => {
+export const compare_alt_68: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "<=");
@@ -2446,7 +2518,7 @@ export const compare_alt_67: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const bitwiseShift_alt_68: Rule = (ctx, b, field) => {
+export const bitwiseShift_alt_69: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "<<");
@@ -2456,7 +2528,7 @@ export const bitwiseShift_alt_68: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const add_alt_69: Rule = (ctx, b, field) => {
+export const add_alt_70: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "+");
@@ -2466,7 +2538,7 @@ export const add_alt_69: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const Unary_star_70: Rule = (ctx, b, field) => {
+export const Unary_star_71: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (
@@ -2485,7 +2557,7 @@ export const Unary_star_70: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const Suffix_star_71: Rule = (ctx, b, field) => {
+export const Suffix_star_72: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (((p = ctx.p), suffix(ctx, b2))) {}
@@ -2495,7 +2567,7 @@ export const Suffix_star_71: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const StructInstanceFields_optional_72: Rule = (ctx, b, field) => {
+export const StructInstanceFields_optional_73: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = commaList(StructFieldInitializer)(ctx, b2);
@@ -2505,7 +2577,27 @@ export const StructInstanceFields_optional_72: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StructFieldInitializer_seq_73: Rule = (ctx, b, field) => {
+export const MapLiteral_optional_74: Rule = (ctx, b, field) => {
+    const b2: Builder = [];
+    const p = ctx.p;
+    let r = commaList(mapField)(ctx, b2);
+    r = r || ((ctx.p = p), true);
+    if (r && b2.length > 0) {
+        b.push(CstNode(b2, field ?? "", field ?? ""));
+    }
+    return r;
+};
+export const SetLiteral_optional_75: Rule = (ctx, b, field) => {
+    const b2: Builder = [];
+    const p = ctx.p;
+    let r = commaList(expression)(ctx, b2);
+    r = r || ((ctx.p = p), true);
+    if (r && b2.length > 0) {
+        b.push(CstNode(b2, field ?? "", field ?? ""));
+    }
+    return r;
+};
+export const StructFieldInitializer_seq_76: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, ":");
@@ -2518,17 +2610,17 @@ export const StructFieldInitializer_seq_73: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StructFieldInitializer_optional_74: Rule = (ctx, b, field) => {
+export const StructFieldInitializer_optional_77: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = StructFieldInitializer_seq_73(ctx, b2);
+    let r = StructFieldInitializer_seq_76(ctx, b2);
     r = r || ((ctx.p = p), true);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const ParameterList_optional_75: (T: Rule) => Rule = (T) => {
+export const ParameterList_optional_78: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
@@ -2540,7 +2632,7 @@ export const ParameterList_optional_75: (T: Rule) => Rule = (T) => {
         return r;
     };
 };
-export const commaList_optional_76: (T: Rule) => Rule = (T) => {
+export const commaList_optional_79: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
@@ -2552,7 +2644,7 @@ export const commaList_optional_76: (T: Rule) => Rule = (T) => {
         return r;
     };
 };
-export const IntegerLiteral_alt_77: Rule = (ctx, b, field) => {
+export const IntegerLiteral_alt_80: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = IntegerLiteralHex(ctx, b2);
@@ -2564,7 +2656,7 @@ export const IntegerLiteral_alt_77: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const IntegerLiteralDec_lex_78: Rule = (ctx, b, field) => {
+export const IntegerLiteralDec_lex_81: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
@@ -2578,7 +2670,7 @@ export const IntegerLiteralDec_lex_78: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const IntegerLiteralHex_seq_79: Rule = (ctx, b, field) => {
+export const IntegerLiteralHex_seq_82: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "0");
@@ -2592,13 +2684,13 @@ export const IntegerLiteralHex_seq_79: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const IntegerLiteralHex_lex_80: Rule = (ctx, b, field) => {
+export const IntegerLiteralHex_lex_83: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
         space: undefined,
     };
-    const r = IntegerLiteralHex_seq_79(newCtx, b2);
+    const r = IntegerLiteralHex_seq_82(newCtx, b2);
     if (r) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2606,7 +2698,7 @@ export const IntegerLiteralHex_lex_80: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const IntegerLiteralBin_seq_81: Rule = (ctx, b, field) => {
+export const IntegerLiteralBin_seq_84: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "0");
@@ -2624,13 +2716,13 @@ export const IntegerLiteralBin_seq_81: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const IntegerLiteralBin_lex_82: Rule = (ctx, b, field) => {
+export const IntegerLiteralBin_lex_85: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
         space: undefined,
     };
-    const r = IntegerLiteralBin_seq_81(newCtx, b2);
+    const r = IntegerLiteralBin_seq_84(newCtx, b2);
     if (r) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2638,7 +2730,7 @@ export const IntegerLiteralBin_lex_82: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const IntegerLiteralOct_seq_83: Rule = (ctx, b, field) => {
+export const IntegerLiteralOct_seq_86: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "0");
@@ -2656,13 +2748,13 @@ export const IntegerLiteralOct_seq_83: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const IntegerLiteralOct_lex_84: Rule = (ctx, b, field) => {
+export const IntegerLiteralOct_lex_87: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
         space: undefined,
     };
-    const r = IntegerLiteralOct_seq_83(newCtx, b2);
+    const r = IntegerLiteralOct_seq_86(newCtx, b2);
     if (r) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2670,7 +2762,7 @@ export const IntegerLiteralOct_lex_84: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const underscored_optional_85: (T: Rule) => Rule = (T) => {
+export const underscored_optional_88: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
@@ -2682,11 +2774,11 @@ export const underscored_optional_85: (T: Rule) => Rule = (T) => {
         return r;
     };
 };
-export const underscored_seq_86: (T: Rule) => Rule = (T) => {
+export const underscored_seq_89: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
-        let r = underscored_optional_85(T)(ctx, b2);
+        let r = underscored_optional_88(T)(ctx, b2);
         r = r && T(ctx, b2);
         if (r && b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -2697,11 +2789,11 @@ export const underscored_seq_86: (T: Rule) => Rule = (T) => {
         return r;
     };
 };
-export const underscored_star_87: (T: Rule) => Rule = (T) => {
+export const underscored_star_90: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         let p = ctx.p;
-        while (((p = ctx.p), underscored_seq_86(T)(ctx, b2))) {}
+        while (((p = ctx.p), underscored_seq_89(T)(ctx, b2))) {}
         ctx.p = p;
         if (b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -2709,12 +2801,12 @@ export const underscored_star_87: (T: Rule) => Rule = (T) => {
         return true;
     };
 };
-export const underscored_seq_88: (T: Rule) => Rule = (T) => {
+export const underscored_seq_91: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
         let r = T(ctx, b2);
-        r = r && underscored_star_87(T)(ctx, b2);
+        r = r && underscored_star_90(T)(ctx, b2);
         if (r && b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
         }
@@ -2724,13 +2816,13 @@ export const underscored_seq_88: (T: Rule) => Rule = (T) => {
         return r;
     };
 };
-export const Id_lookneg_89: Rule = (ctx, b, field) => {
+export const Id_lookneg_92: Rule = (ctx, b, field) => {
     const p = ctx.p;
     const r = reservedWord(ctx, b);
     ctx.p = p;
     return !r;
 };
-export const Id_star_90: Rule = (ctx, b, field) => {
+export const Id_star_93: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (((p = ctx.p), idPart(ctx, b2))) {}
@@ -2740,10 +2832,10 @@ export const Id_star_90: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const Id_seq_91: Rule = (ctx, b, field) => {
+export const Id_seq_94: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = Id_lookneg_89(ctx, b2);
+    let r = Id_lookneg_92(ctx, b2);
     r =
         r &&
         consumeClass(
@@ -2752,7 +2844,7 @@ export const Id_seq_91: Rule = (ctx, b, field) => {
             (c) =>
                 (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_",
         );
-    r = r && Id_star_90(ctx, b2);
+    r = r && Id_star_93(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2761,22 +2853,22 @@ export const Id_seq_91: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const Id_stringify_92: Rule = (ctx, b, field) => {
+export const Id_stringify_95: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = Id_seq_91(ctx, []);
+    const r = Id_seq_94(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const Id_lex_93: Rule = (ctx, b, field) => {
+export const Id_lex_96: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
         space: undefined,
     };
-    const r = Id_stringify_92(newCtx, b2);
+    const r = Id_stringify_95(newCtx, b2);
     if (r) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2784,7 +2876,7 @@ export const Id_lex_93: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const FuncId_optional_94: Rule = (ctx, b, field) => {
+export const FuncId_optional_97: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeClass(ctx, b2, (c) => c === "." || c === "~");
@@ -2794,7 +2886,7 @@ export const FuncId_optional_94: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const FuncId_plus_95: Rule = (ctx, b, field) => {
+export const FuncId_plus_98: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const r = consumeClass(
         ctx,
@@ -2818,11 +2910,11 @@ export const FuncId_plus_95: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const FuncId_seq_96: Rule = (ctx, b, field) => {
+export const FuncId_seq_99: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "`");
-    r = r && FuncId_plus_95(ctx, b2);
+    r = r && FuncId_plus_98(ctx, b2);
     r = r && consumeString(ctx, b2, "`");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -2832,7 +2924,7 @@ export const FuncId_seq_96: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const FuncId_plus_97: Rule = (ctx, b, field) => {
+export const FuncId_plus_100: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const r = consumeClass(
         ctx,
@@ -2884,26 +2976,26 @@ export const FuncId_plus_97: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const FuncId_alt_98: Rule = (ctx, b, field) => {
+export const FuncId_alt_101: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = FuncId_seq_96(ctx, b2);
-    r = r || ((ctx.p = p), FuncId_plus_97(ctx, b2));
+    let r = FuncId_seq_99(ctx, b2);
+    r = r || ((ctx.p = p), FuncId_plus_100(ctx, b2));
     if (b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const FuncId_stringify_99: Rule = (ctx, b, field) => {
+export const FuncId_stringify_102: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = FuncId_alt_98(ctx, []);
+    const r = FuncId_alt_101(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const BoolLiteral_alt_100: Rule = (ctx, b, field) => {
+export const BoolLiteral_alt_103: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "true");
@@ -2913,13 +3005,13 @@ export const BoolLiteral_alt_100: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const BoolLiteral_lookneg_101: Rule = (ctx, b, field) => {
+export const BoolLiteral_lookneg_104: Rule = (ctx, b, field) => {
     const p = ctx.p;
     const r = idPart(ctx, b);
     ctx.p = p;
     return !r;
 };
-export const StringLiteral_seq_102: Rule = (ctx, b, field) => {
+export const StringLiteral_seq_105: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "\\");
@@ -2932,40 +3024,40 @@ export const StringLiteral_seq_102: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StringLiteral_alt_103: Rule = (ctx, b, field) => {
+export const StringLiteral_alt_106: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeClass(ctx, b2, (c) => !(c === '"' || c === "\\"));
-    r = r || ((ctx.p = p), StringLiteral_seq_102(ctx, b2));
+    r = r || ((ctx.p = p), StringLiteral_seq_105(ctx, b2));
     if (b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const StringLiteral_star_104: Rule = (ctx, b, field) => {
+export const StringLiteral_star_107: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
-    while (((p = ctx.p), StringLiteral_alt_103(ctx, b2))) {}
+    while (((p = ctx.p), StringLiteral_alt_106(ctx, b2))) {}
     ctx.p = p;
     if (b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return true;
 };
-export const StringLiteral_stringify_105: Rule = (ctx, b, field) => {
+export const StringLiteral_stringify_108: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = StringLiteral_star_104(ctx, []);
+    const r = StringLiteral_star_107(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const StringLiteral_seq_106: Rule = (ctx, b, field) => {
+export const StringLiteral_seq_109: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, '"');
-    r = r && StringLiteral_stringify_105(ctx, b2);
+    r = r && StringLiteral_stringify_108(ctx, b2);
     r = r && consumeString(ctx, b2, '"');
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -2975,13 +3067,13 @@ export const StringLiteral_seq_106: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const StringLiteral_lex_107: Rule = (ctx, b, field) => {
+export const StringLiteral_lex_110: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
         space: undefined,
     };
-    const r = StringLiteral_seq_106(newCtx, b2);
+    const r = StringLiteral_seq_109(newCtx, b2);
     if (r) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -2989,7 +3081,7 @@ export const StringLiteral_lex_107: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const escapeChar_optional_108: Rule = (ctx, b, field) => {
+export const escapeChar_optional_111: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -2999,7 +3091,7 @@ export const escapeChar_optional_108: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_optional_109: Rule = (ctx, b, field) => {
+export const escapeChar_optional_112: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -3009,7 +3101,7 @@ export const escapeChar_optional_109: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_optional_110: Rule = (ctx, b, field) => {
+export const escapeChar_optional_113: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -3019,7 +3111,7 @@ export const escapeChar_optional_110: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_optional_111: Rule = (ctx, b, field) => {
+export const escapeChar_optional_114: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -3029,7 +3121,7 @@ export const escapeChar_optional_111: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_optional_112: Rule = (ctx, b, field) => {
+export const escapeChar_optional_115: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -3039,15 +3131,15 @@ export const escapeChar_optional_112: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_seq_113: Rule = (ctx, b, field) => {
+export const escapeChar_seq_116: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
-    r = r && escapeChar_optional_108(ctx, b2);
-    r = r && escapeChar_optional_109(ctx, b2);
-    r = r && escapeChar_optional_110(ctx, b2);
     r = r && escapeChar_optional_111(ctx, b2);
     r = r && escapeChar_optional_112(ctx, b2);
+    r = r && escapeChar_optional_113(ctx, b2);
+    r = r && escapeChar_optional_114(ctx, b2);
+    r = r && escapeChar_optional_115(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -3056,20 +3148,20 @@ export const escapeChar_seq_113: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_stringify_114: Rule = (ctx, b, field) => {
+export const escapeChar_stringify_117: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = escapeChar_seq_113(ctx, []);
+    const r = escapeChar_seq_116(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const escapeChar_seq_115: Rule = (ctx, b, field) => {
+export const escapeChar_seq_118: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "u{");
-    r = r && escapeChar_stringify_114(ctx, b2);
+    r = r && escapeChar_stringify_117(ctx, b2);
     r = r && consumeString(ctx, b2, "}");
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -3079,7 +3171,7 @@ export const escapeChar_seq_115: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_seq_116: Rule = (ctx, b, field) => {
+export const escapeChar_seq_119: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -3094,20 +3186,20 @@ export const escapeChar_seq_116: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_stringify_117: Rule = (ctx, b, field) => {
+export const escapeChar_stringify_120: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = escapeChar_seq_116(ctx, []);
+    const r = escapeChar_seq_119(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const escapeChar_seq_118: Rule = (ctx, b, field) => {
+export const escapeChar_seq_121: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "u");
-    r = r && escapeChar_stringify_117(ctx, b2);
+    r = r && escapeChar_stringify_120(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -3116,7 +3208,7 @@ export const escapeChar_seq_118: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_seq_119: Rule = (ctx, b, field) => {
+export const escapeChar_seq_122: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = hexDigit(ctx, b2);
@@ -3129,20 +3221,20 @@ export const escapeChar_seq_119: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const escapeChar_stringify_120: Rule = (ctx, b, field) => {
+export const escapeChar_stringify_123: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = escapeChar_seq_119(ctx, []);
+    const r = escapeChar_seq_122(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const escapeChar_seq_121: Rule = (ctx, b, field) => {
+export const escapeChar_seq_124: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "x");
-    r = r && escapeChar_stringify_120(ctx, b2);
+    r = r && escapeChar_stringify_123(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -3151,7 +3243,7 @@ export const escapeChar_seq_121: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const keyword_lookneg_122: (T: Rule) => Rule = (T) => {
+export const keyword_lookneg_125: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const p = ctx.p;
         const r = idPart(ctx, b);
@@ -3159,12 +3251,12 @@ export const keyword_lookneg_122: (T: Rule) => Rule = (T) => {
         return !r;
     };
 };
-export const keyword_seq_123: (T: Rule) => Rule = (T) => {
+export const keyword_seq_126: (T: Rule) => Rule = (T) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
         let r = T(ctx, b2);
-        r = r && keyword_lookneg_122(T)(ctx, b2);
+        r = r && keyword_lookneg_125(T)(ctx, b2);
         if (r && b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
         }
@@ -3174,7 +3266,7 @@ export const keyword_seq_123: (T: Rule) => Rule = (T) => {
         return r;
     };
 };
-export const reservedWord_alt_124: Rule = (ctx, b, field) => {
+export const reservedWord_alt_127: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
     let r = consumeString(ctx, b2, "extend");
@@ -3214,7 +3306,7 @@ export const reservedWord_alt_124: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const space_plus_125: Rule = (ctx, b, field) => {
+export const space_plus_128: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const r = consumeClass(
         ctx,
@@ -3238,22 +3330,22 @@ export const space_plus_125: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const space_stringify_126: Rule = (ctx, b, field) => {
+export const space_stringify_129: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = space_plus_125(ctx, []);
+    const r = space_plus_128(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const space_lex_127: Rule = (ctx, b, field) => {
+export const space_lex_130: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const newCtx = {
         ...ctx,
         space: undefined,
     };
-    const r = space_stringify_126(newCtx, b2);
+    const r = space_stringify_129(newCtx, b2);
     if (r) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
@@ -3261,26 +3353,26 @@ export const space_lex_127: Rule = (ctx, b, field) => {
     skip(ctx, b);
     return r;
 };
-export const space_alt_128: Rule = (ctx, b, field) => {
+export const space_alt_131: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = space_lex_127(ctx, b2);
+    let r = space_lex_130(ctx, b2);
     r = r || ((ctx.p = p), Comment(ctx, b2));
     if (b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return r;
 };
-export const multiLineComment_lookneg_129: Rule = (ctx, b, field) => {
+export const multiLineComment_lookneg_132: Rule = (ctx, b, field) => {
     const p = ctx.p;
     const r = consumeString(ctx, b, "*/");
     ctx.p = p;
     return !r;
 };
-export const multiLineComment_seq_130: Rule = (ctx, b, field) => {
+export const multiLineComment_seq_133: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     const p = ctx.p;
-    let r = multiLineComment_lookneg_129(ctx, b2);
+    let r = multiLineComment_lookneg_132(ctx, b2);
     r = r && consumeAny(ctx, b2);
     if (r && b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
@@ -3290,26 +3382,26 @@ export const multiLineComment_seq_130: Rule = (ctx, b, field) => {
     }
     return r;
 };
-export const multiLineComment_star_131: Rule = (ctx, b, field) => {
+export const multiLineComment_star_134: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
-    while (((p = ctx.p), multiLineComment_seq_130(ctx, b2))) {}
+    while (((p = ctx.p), multiLineComment_seq_133(ctx, b2))) {}
     ctx.p = p;
     if (b2.length > 0) {
         b.push(CstNode(b2, field ?? "", field ?? ""));
     }
     return true;
 };
-export const multiLineComment_stringify_132: Rule = (ctx, b, field) => {
+export const multiLineComment_stringify_135: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = multiLineComment_star_131(ctx, []);
+    const r = multiLineComment_star_134(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const singleLineComment_star_133: Rule = (ctx, b, field) => {
+export const singleLineComment_star_136: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (
@@ -3321,16 +3413,16 @@ export const singleLineComment_star_133: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const singleLineComment_stringify_134: Rule = (ctx, b, field) => {
+export const singleLineComment_stringify_137: Rule = (ctx, b, field) => {
     const p = ctx.p;
-    const r = singleLineComment_star_133(ctx, []);
+    const r = singleLineComment_star_136(ctx, []);
     if (r) {
         const text = ctx.s.substring(p, ctx.p);
         b.push(CstLeaf(text));
     }
     return r;
 };
-export const JustImports_star_135: Rule = (ctx, b, field) => {
+export const JustImports_star_138: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (((p = ctx.p), Import(ctx, b2))) {}
@@ -3340,7 +3432,7 @@ export const JustImports_star_135: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const JustImports_star_136: Rule = (ctx, b, field) => {
+export const JustImports_star_139: Rule = (ctx, b, field) => {
     const b2: Builder = [];
     let p = ctx.p;
     while (((p = ctx.p), consumeAny(ctx, b2))) {}
@@ -3350,7 +3442,7 @@ export const JustImports_star_136: Rule = (ctx, b, field) => {
     }
     return true;
 };
-export const inter_seq_137: (A: Rule, B: Rule) => Rule = (A, B) => {
+export const inter_seq_140: (A: Rule, B: Rule) => Rule = (A, B) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         const p = ctx.p;
@@ -3365,11 +3457,11 @@ export const inter_seq_137: (A: Rule, B: Rule) => Rule = (A, B) => {
         return r;
     };
 };
-export const inter_star_138: (A: Rule, B: Rule) => Rule = (A, B) => {
+export const inter_star_141: (A: Rule, B: Rule) => Rule = (A, B) => {
     return (ctx, b, field) => {
         const b2: Builder = [];
         let p = ctx.p;
-        while (((p = ctx.p), inter_seq_137(A, B)(ctx, b2))) {}
+        while (((p = ctx.p), inter_seq_140(A, B)(ctx, b2))) {}
         ctx.p = p;
         if (b2.length > 0) {
             b.push(CstNode(b2, field ?? "", field ?? ""));
diff --git a/src/fmt/formatter/format-expressions.ts b/src/fmt/formatter/format-expressions.ts
index 57ea78fa..4a762bd7 100644
--- a/src/fmt/formatter/format-expressions.ts
+++ b/src/fmt/formatter/format-expressions.ts
@@ -4,6 +4,7 @@ import {
     childByType,
     childIdxByField,
     childLeafIdxWithText,
+    childrenByField,
     containsComments,
     countNewlines,
     filterComments,
@@ -128,6 +129,10 @@ export const formatExpression = (code: CodeBuilder, node: Cst): void => {
             code.apply(formatId, node);
             return;
         }
+        case "MapLiteral": {
+            formatMapLiteral(code, node);
+            return;
+        }
     }
 
     code.add(visit(node).trim());
@@ -583,3 +588,85 @@ const formatSuffixCall: FormatRule = (code, node) => {
 
     formatTrailingComments(code, args, endIndex, true);
 };
+
+const formatTypeArgs: FormatRule = (code, node) => {
+    // <Int , String, Foo>
+    //  ^^^ ^^^^^^^^^^^^^
+    //  |   |
+    //  |   tail
+    //  head
+    const head = childByField(node, "head");
+    const tail = childByField(node, "tail");
+
+    if (!head) {
+        throw new Error("Invalid type args");
+    }
+
+    code.add("<");
+    code.apply(formatType, head);
+
+    if (tail) {
+        const right = childrenByField(tail, "right");
+        right.forEach((type) => {
+            code.add(", ");
+            code.apply(formatType, type);
+        });
+    }
+
+    code.add(">");
+};
+
+const formatMapLiteral: FormatRule = (code, node) => {
+    // map<Int, Int> { }
+    // ^^^^^^^^^^^^^ ^ ^
+    // |             | |
+    // |             | closeBrace
+    // |             openBrace
+    // typeArgs
+    const typeArgs = childByField(node, "typeArgs");
+    const openBrace = childLeafIdxWithText(node, "{");
+    const closeBrace = childLeafIdxWithText(node, "}");
+
+    if (!typeArgs || openBrace === -1 || closeBrace === -1) {
+        throw new Error("Invalid map literal");
+    }
+
+    code.add("map");
+    code.apply(formatTypeArgs, typeArgs);
+    code.add(" ");
+
+    formatSeparatedList(
+        code,
+        node,
+        (code, item) => {
+            const key = childByField(item, "key");
+            const value = childByField(item, "value");
+
+            if (!key || !value) {
+                throw new Error("Invalid map literal field");
+            }
+
+            code.apply(formatExpression, key);
+            code.add(": ");
+            code.apply(formatExpression, value);
+        },
+        {
+            startIndex: openBrace,
+            endIndex: closeBrace,
+            wrapperLeft: "{",
+            wrapperRight: "}",
+            extraWrapperSpace: " ",
+            provideTrailingComments: (field) => {
+                if (field.$ !== "node") return [];
+
+                // 10: 20
+                //     ^^ this
+                const value = childByField(field, "value");
+                if (!value) return [];
+
+                const endIndex = childIdxByField(field, "value");
+                return filterComments(field.children.slice(endIndex));
+            },
+        },
+    );
+};
diff --git a/src/fmt/formatter/format-statements.ts b/src/fmt/formatter/format-statements.ts
index e7ed6c22..0b78e648 100644
--- a/src/fmt/formatter/format-statements.ts
+++ b/src/fmt/formatter/format-statements.ts
@@ -1,4 +1,4 @@
-import type { CstLeaf, CstNode } from "@/fmt/cst/cst-parser";
+import type { Cst, CstLeaf, CstNode } from "@/fmt/cst/cst-parser";
 import {
     childByField,
     childByType,
@@ -7,6 +7,7 @@ import {
     childLeafWithText,
     containsComments,
     filterComments,
+    isComment,
     isInlineComment,
     nonLeafChild,
     trailingNewlines,
@@ -199,14 +200,11 @@ export const formatStatement: FormatStatementRule = (
     }
 };
 
-function formatCommentsBetweenAssignAndValue(
+function formatInBetweenComments(
     code: CodeBuilder,
-    node: CstNode,
-    assign: CstLeaf,
-    init: CstNode,
-): boolean {
-    const commentsAndNewlines = getLeafsBetween(node, assign, init);
-    const comments = filterComments(commentsAndNewlines);
+    comments: CstNode[],
+    commentsAndNewlines: readonly Cst[],
+) {
     if (comments.length > 0) {
         const multiline = multilineComments(commentsAndNewlines);
 
@@ -235,6 +233,17 @@ function formatCommentsBetweenAssignAndValue(
     return false;
 }
 
+function formatCommentsBetweenAssignAndValue(
+    code: CodeBuilder,
+    node: CstNode,
+    assign: CstLeaf,
+    init: CstNode,
+): boolean {
+    const commentsAndNewlines = getLeafsBetween(node, assign, init);
+    const comments = filterComments(commentsAndNewlines);
+    return formatInBetweenComments(code, comments, commentsAndNewlines);
+}
+
 const formatLetStatement: FormatStatementRule = (code, node, needSemicolon) => {
     // let name : Int = 100;
     //     ^^^^ ^^^^^ ^ ^^^
@@ -392,36 +401,39 @@ const formatAssignStatement: FormatStatementRule = (
     node,
     needSemicolon,
 ) => {
-    // value + = 100;
-    // ^^^^^ ^ ^ ^^^
-    // |     | | |
-    // |     | | right
-    // |     | assign
-    // |     operatorOpt
+    // value += 100;
+    // ^^^^^ ^^ ^^^
+    // |     |  |
+    // |     |  right
+    // |     |
+    // |     operator
     // left
     const left = childByField(node, "left");
-    const operatorOpt = childByField(node, "operator");
-    const assign = childLeafWithText(node, "=");
+    const operator = childByField(node, "operator");
     const right = childByField(node, "right");
 
-    if (!left || !right || !assign) {
+    if (!left || !right || !operator) {
         throw new Error("Invalid assign statement");
     }
 
     formatExpression(code, left);
 
     code.space();
-    if (operatorOpt) {
-        code.add(visit(operatorOpt).trim());
-    }
 
-    code.add("=");
+    const assign = operator.children.at(0);
+    if (assign && assign.$ === "leaf") {
+        code.add(assign.text);
+    } else {
+        code.add("=");
+    }
 
-    const hasMultilineComment = formatCommentsBetweenAssignAndValue(
+    const comments = operator.children
+        .filter((it) => it.$ === "node")
+        .filter((it) => isComment(it));
+    const hasMultilineComment = formatInBetweenComments(
         code,
-        node,
-        assign,
-        right,
+        comments,
+        operator.children,
     );
     const needIndentAndNewline = multilineExpression(right);
     if (needIndentAndNewline || hasMultilineComment) {
diff --git a/src/fmt/formatter/helpers.ts b/src/fmt/formatter/helpers.ts
index 046deb31..97f779fc 100644
--- a/src/fmt/formatter/helpers.ts
+++ b/src/fmt/formatter/helpers.ts
@@ -375,7 +375,7 @@ export function containsSeveralNewlines(text: string): boolean {
     return text.slice(index + 1).includes("\n");
 }
 
-export function multilineComments(comments: Cst[]): boolean {
+export function multilineComments(comments: readonly Cst[]): boolean {
     return comments.some((it) => visit(it).includes("\n"));
 }
 
diff --git a/src/generator/writeProgram.ts b/src/generator/writeProgram.ts
index d3a8a29e..e00a6ba2 100644
--- a/src/generator/writeProgram.ts
+++ b/src/generator/writeProgram.ts
@@ -64,7 +64,7 @@ export async function writeProgram(
         .filter((it) => it.loc.origin === "user")
         .map((it) => ({
             name: it.name,
-            value: writeTypescriptValue(it.value),
+            value: writeTypescriptValue(ctx, it.value, it.type, it.loc),
             fromContract: false,
         }));
 
@@ -205,7 +205,7 @@ export async function writeProgram(
             constants.push(
                 ...t.constants.map((it) => ({
                     name: it.name,
-                    value: writeTypescriptValue(it.value),
+                    value: writeTypescriptValue(ctx, it.value, it.type, it.loc),
                     fromContract: true,
                 })),
             );
diff --git a/src/generator/writers/writeExpression.ts b/src/generator/writers/writeExpression.ts
index 8b5341ad..fc0e6fc1 100644
--- a/src/generator/writers/writeExpression.ts
+++ b/src/generator/writers/writeExpression.ts
@@ -41,6 +41,15 @@ import {
 } from "@/ast/ast-helpers";
 import { enabledDebug, enabledNullChecks } from "@/config/features";
 import type { CompilerContext } from "@/context/context";
+import {
+    getKeyParser,
+    getValueParser,
+    mapSerializers,
+} from "@/bindings/typescript/serializers";
+import { getMapAbi } from "@/types/resolveABITypeRef";
+import type { SrcInfo } from "@/grammar";
+import { Cell } from "@ton/core";
+import { makeVisitor } from "@/utils/tricks";
 
 function isNull(wCtx: WriterContext, expr: Ast.Expression): boolean {
     return getExpType(wCtx.ctx, expr).kind === "null";
@@ -186,8 +195,14 @@ export function writeValue(
             }
             return value;
         }
-        default:
-            throwInternalCompilerError("Unrecognized ast literal kind");
+        case "map_value": {
+            if (typeof val.bocHex === "undefined") {
+                return "null()";
+            }
+            const res = writeCell(Cell.fromHex(val.bocHex), wCtx);
+            wCtx.used(res);
+            return `${res}()`;
+        }
     }
 }
 
@@ -195,36 +210,9 @@ export function writePathExpression(path: Ast.Id[]): string {
     return [funcIdOf(idText(path[0]!)), ...path.slice(1).map(idText)].join(`'`);
 }
 
-export function writeExpression(
-    f: Ast.Expression,
-    wCtx: WriterContext,
-): string {
-    // literals and constant expressions are covered here
-
-    // FIXME: Once optimization step is added, remove this try and replace it with this
-    // conditional:
-    // if (isLiteral(f)) {
-    //    return writeValue(f, wCtx);
-    // }
-    try {
-        const util = getAstUtil(getAstFactory());
-        // Let us put a limit of 2 ^ 12 = 4096 iterations on loops to increase compiler responsiveness.
-        // If a loop takes more than such number of iterations, the interpreter will fail evaluation.
-        // I think maxLoopIterations should be a command line option in case a user wants to wait more
-        // during evaluation.
-        const value = evalConstantExpression(f, wCtx.ctx, util, {
-            maxLoopIterations: 2n ** 12n,
-        });
-        return writeValue(value, false, wCtx);
-    } catch (error) {
-        if (!(error instanceof TactConstEvalError) || error.fatal) throw error;
-    }
-
-    //
-    // ID Reference
-    //
-
-    if (f.kind === "id") {
+const writeIdExpr =
+    (f: Ast.Id) =>
+    (wCtx: WriterContext): string => {
         const t = getExpType(wCtx.ctx, f);
 
         // Handle packed type
@@ -259,10 +247,14 @@ export function writeExpression(
         }
 
         return funcIdOf(f.text);
-    }
-
-    // NOTE: We always wrap expressions in parentheses to avoid operator precedence issues
-    if (f.kind === "op_binary") {
+    };
+
+/**
+ * NOTE: We always wrap expressions in parentheses to avoid operator precedence issues
+ */
+const writeBinaryExpr =
+    (f: Ast.OpBinary) =>
+    (wCtx: WriterContext): string => {
         // Special case for non-integer types and nullable
         if (f.op === "==" || f.op === "!=") {
             if (isNull(wCtx, f.left) && isNull(wCtx, f.right)) {
@@ -424,14 +416,15 @@ export function writeExpression(
             writeExpression(f.right, wCtx) +
             ")"
         );
-    }
-
-    //
-    // Unary operations: !, -, +, !!
-    // NOTE: We always wrap expressions in parentheses to avoid operator precedence issues
-    //
-
-    if (f.kind === "op_unary") {
+    };
+
+/**
+ * Unary operations: !, -, +, !!
+ * NOTE: We always wrap expressions in parentheses to avoid operator precedence issues
+ */
+const writeUnaryExpr =
+    (f: Ast.OpUnary) =>
+    (wCtx: WriterContext): string => {
         // NOTE: Logical not is written as a bitwise not
         switch (f.op) {
             case "!": {
@@ -469,14 +462,15 @@ export function writeExpression(
                 }
             }
         }
-    }
-
-    //
-    // Field Access
-    // NOTE: this branch resolves "a.b", where "a" is an expression and "b" is a field name
-    //
-
-    if (f.kind === "field_access") {
+    };
+
+/**
+ * Field Access
+ * NOTE: this branch resolves "a.b", where "a" is an expression and "b" is a field name
+ */
+const writeFieldExpr =
+    (f: Ast.FieldAccess) =>
+    (wCtx: WriterContext): string => {
         // Optimize Context().sender to sender()
         // This is a special case to improve gas efficiency
         if (
@@ -547,13 +541,11 @@ export function writeExpression(
                 wCtx,
             );
         }
-    }
+    };
 
-    //
-    // Static Function Call
-    //
-
-    if (f.kind === "static_call") {
+const writeStaticCall =
+    (f: Ast.StaticCall) =>
+    (wCtx: WriterContext): string => {
         // Check global functions
         if (GlobalFunctions.has(idText(f.function))) {
             return GlobalFunctions.get(idText(f.function))!.generate(
@@ -584,13 +576,11 @@ export function writeExpression(
                 .join(", ") +
             ")"
         );
-    }
-
-    //
-    // Struct Constructor
-    //
+    };
 
-    if (f.kind === "struct_instance") {
+const writeStructInstance =
+    (f: Ast.StructInstance) =>
+    (wCtx: WriterContext): string => {
         const src = getType(wCtx.ctx, f.type);
 
         // Write a constructor
@@ -612,13 +602,11 @@ export function writeExpression(
             wCtx,
         );
         return `${id}(${expressions.join(", ")})`;
-    }
-
-    //
-    // Object-based function call
-    //
+    };
 
-    if (f.kind === "method_call") {
+const writeMethodCall =
+    (f: Ast.MethodCall) =>
+    (wCtx: WriterContext): string => {
         // Resolve source type
         const selfTyRef = getExpType(wCtx.ctx, f.self);
 
@@ -759,38 +747,32 @@ export function writeExpression(
             `Cannot call function of non - direct type: "${printTypeRef(selfTyRef)}"`,
             f.loc,
         );
-    }
-
-    //
-    // Init of
-    //
+    };
 
-    if (f.kind === "init_of") {
+const writeInitOf =
+    (f: Ast.InitOf) =>
+    (wCtx: WriterContext): string => {
         const type = getType(wCtx.ctx, f.contract);
         const initArgs = f.args.map((a, i) =>
             writeCastedExpression(a, type.init!.params[i]!.type, wCtx),
         );
         return `${ops.contractInitChild(idText(f.contract), wCtx)}(${initArgs.join(", ")})`;
-    }
+    };
 
-    //
-    // Code of
-    //
-
-    if (f.kind === "code_of") {
+const writeCodeOf =
+    (f: Ast.CodeOf) =>
+    (wCtx: WriterContext): string => {
         // In case of using `codeOf T` in contract `T`, we simply use MYCODE.
         if (wCtx.name === f.contract.text) {
             return `my_code()`;
         }
 
         return `${ops.contractCodeChild(idText(f.contract), wCtx)}()`;
-    }
-
-    //
-    // Ternary operator
-    //
+    };
 
-    if (f.kind === "conditional") {
+const writeConditional =
+    (f: Ast.Conditional) =>
+    (wCtx: WriterContext): string => {
         const thenType = getExpType(wCtx.ctx, f.thenBranch);
         const elseType = getExpType(wCtx.ctx, f.elseBranch);
 
@@ -829,13 +811,144 @@ export function writeExpression(
 
         // Default case
         return `(${writeExpressionInCondition(f.condition, wCtx)} ? ${writeExpression(f.thenBranch, wCtx)} : ${writeExpression(f.elseBranch, wCtx)})`;
-    }
+    };
 
-    //
-    // Unreachable
-    //
+const cannotHappen =
+    (_f: Ast.Expression) =>
+    (_wCtx: WriterContext): string => {
+        return throwInternalCompilerError(
+            "Const evaluator must have handled these cases before",
+        );
+    };
+const unsupported =
+    (_f: Ast.Expression) =>
+    (_wCtx: WriterContext): string => {
+        return throwInternalCompilerError("Set literals are not supported");
+    };
+
+const writeMapLiteral =
+    (node: Ast.MapLiteral) =>
+    (_ctx: WriterContext): string => {
+        throwCompilationError(
+            "Only constant map literals are supported",
+            node.loc,
+        );
+        // NB! Intentionally left here for when we can distinguish which Ast.Id are variables
+        // const { fields, loc } = node;
+
+        // const fnName = freshIdentifier(`map_literal_`);
+        // const varName = freshIdentifier(`$fresh`);
+
+        // const mapType = getExpType(ctx.ctx, node);
+        // if (mapType.kind !== 'map') {
+        //     throwInternalCompilerError("Map literal doesn't have map type");
+        // }
+
+        // const used: Set<string> = new Set();
+        // const params: string[] = [];
+        // const args: string[] = [];
+        // traverseAndCheck(node, (child) => {
+        //     if (child.kind === 'static_call' || child.kind === 'struct_instance') {
+        //         return false;
+        //     }
+        //     if (child.kind !== 'id') {
+        //         return true;
+        //     }
+        //     if (used.has(child.text)) {
+        //         return false;
+        //     }
+        //     used.add(child.text);
+        //     const type = resolveFuncType(
+        //         getExpType(ctx.ctx, child),
+        //         ctx,
+        //     );
+        //     const name = funcIdOf(child.text);
+        //     params.push(`${type} ${name}`);
+        //     args.push(name);
+        //     return false;
+        // });
+
+        // ctx.fun(fnName, () => {
+        //     ctx.signature(`cell ${fnName}(${params.join(', ')})`);
+        //     ctx.flag("impure");
+        //     ctx.flag("inline");
+        //     ctx.body(() => {
+        //         ctx.append(`cell ${varName} = null();`);
+        //         for (const field of fields) {
+        //             const callCode = generateSet(
+        //                 ctx,
+        //                 loc,
+        //                 mapType,
+        //                 getExpType(ctx.ctx, field.value),
+        //                 [
+        //                     varName,
+        //                     writeExpression(field.key, ctx),
+        //                     writeExpression(field.value, ctx),
+        //                 ],
+        //             );
+        //             ctx.append(`${callCode};`);
+        //         }
+        //         ctx.append(`return ${varName};`);
+        //     });
+        // });
+
+        // ctx.used(fnName);
+
+        // return `${fnName}(${args.join(', ')})`;
+    };
+
+const writeExpressionAux: (
+    f: Ast.Expression,
+) => (wCtx: WriterContext) => string = makeVisitor<Ast.Expression>()({
+    id: writeIdExpr,
+    op_binary: writeBinaryExpr,
+    op_unary: writeUnaryExpr,
+    field_access: writeFieldExpr,
+    static_call: writeStaticCall,
+    struct_instance: writeStructInstance,
+    method_call: writeMethodCall,
+    init_of: writeInitOf,
+    code_of: writeCodeOf,
+    conditional: writeConditional,
+    map_literal: writeMapLiteral,
+    string: cannotHappen,
+    number: cannotHappen,
+    boolean: cannotHappen,
+    null: cannotHappen,
+    address: cannotHappen,
+    cell: cannotHappen,
+    slice: cannotHappen,
+    map_value: cannotHappen,
+    struct_value: cannotHappen,
+    set_literal: unsupported,
+});
 
-    throw Error("Unknown expression");
+export function writeExpression(
+    f: Ast.Expression,
+    wCtx: WriterContext,
+): string {
+    // literals and constant expressions are covered here
+
+    // FIXME: Once optimization step is added, remove this try and replace it with this
+    // conditional:
+    // if (isLiteral(f)) {
+    //    return writeValue(f, wCtx);
+    // }
+    try {
+        const util = getAstUtil(getAstFactory());
+        // Let us put a limit of 2 ^ 12 = 4096 iterations on loops to increase compiler responsiveness.
+        // If a loop takes more than such number of iterations, the interpreter will fail evaluation.
+        // I think maxLoopIterations should be a command line option in case a user wants to wait more
+        // during evaluation.
+        const value = evalConstantExpression(f, wCtx.ctx, util, {
+            maxLoopIterations: 2n ** 12n,
+        });
+        return writeValue(value, false, wCtx);
+    } catch (error) {
+        if (!(error instanceof TactConstEvalError) || error.fatal) throw error;
+    }
+
+    return writeExpressionAux(f)(wCtx);
 }
 
 // Evaluate the `expr` expression and return the resulting literal,
@@ -894,7 +1007,10 @@ export function writeExpressionInCondition(
 }
 
 export function writeTypescriptValue(
+    ctx: CompilerContext,
     val: Ast.Literal | undefined,
+    type: TypeRef,
+    loc: SrcInfo,
 ): string | undefined {
     if (typeof val === "undefined") return undefined;
 
@@ -914,16 +1030,46 @@ export function writeTypescriptValue(
         case "null":
             return "null";
         case "struct_value": {
+            if (type.kind !== "ref") {
+                throwInternalCompilerError("Map value must have map type");
+            }
             const typeName = val.type.text;
+            const structType = getType(ctx, type.name);
             const args = val.args
-                .map(
-                    (it) =>
+                .map((it) => {
+                    const field = structType.fields.find(
+                        (field) => field.name === it.field.text,
+                    );
+                    if (typeof field === "undefined") {
+                        throwInternalCompilerError(
+                            `Field "${it.field.text}" not found in type`,
+                        );
+                    }
+                    return (
                         it.field.text +
                         ": " +
-                        writeTypescriptValue(it.initializer),
-                )
+                        writeTypescriptValue(
+                            ctx,
+                            it.initializer,
+                            field.type,
+                            field.loc,
+                        )
+                    );
+                })
                 .join(", ");
             return `{ $$type: "${typeName}" as const, ${args} }`;
         }
+        case "map_value": {
+            if (type.kind !== "map") {
+                throwInternalCompilerError("Map value must have map type");
+            }
+            const res = mapSerializers.abiMatcher(getMapAbi(type, loc));
+            if (res === null) {
+                throwInternalCompilerError("Wrong map ABI");
+            }
+            const keyType = getKeyParser(res.key);
+            const valueType = getValueParser(res.value);
+            return `Dictionary.loadDirect(${keyType}, ${valueType}, Cell.fromHex("${val.bocHex}").beginParse())`;
+        }
     }
 }
diff --git a/src/grammar/__snapshots__/grammar.spec.ts.snap b/src/grammar/__snapshots__/grammar.spec.ts.snap
index 9e6a0fa1..99ae1ca9 100644
--- a/src/grammar/__snapshots__/grammar.spec.ts.snap
+++ b/src/grammar/__snapshots__/grammar.spec.ts.snap
@@ -37,7 +37,7 @@ exports[`should fail asm-getter.fail 1`] = `
 `;
 
 exports[`should fail augmented-assign-with-space.fail 1`] = `
-"<unknown>:7:14: Expected "!", "(", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "null", "true", "~", capitalized identifier, digit, or identifier
+"<unknown>:7:14: Expected "!", "(", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "map", "null", "set", "true", "~", capitalized identifier, digit, or identifier
   6 |     fun foo() {
 > 7 |         a << = 10;
                    ^
@@ -171,7 +171,7 @@ exports[`should fail contract-empty-traits-list-with-keyword.fail 1`] = `
 `;
 
 exports[`should fail contract-getter-parens-no-method-id.fail 1`] = `
-"<unknown>:2:9: Expected "!", "(", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "null", "true", "~", capitalized identifier, digit, or identifier
+"<unknown>:2:9: Expected "!", "(", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "map", "null", "set", "true", "~", capitalized identifier, digit, or identifier
   1 | contract Test {
 > 2 |     get() fun test(): Int {
               ^
@@ -224,7 +224,7 @@ exports[`should fail empty-bounced-receiver.fail 1`] = `
 `;
 
 exports[`should fail expr-fun-call-trailing-comma-no-args.fail 1`] = `
-"<unknown>:6:14: Expected "!", "(", ")", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "null", "true", "~", capitalized identifier, digit, or identifier
+"<unknown>:6:14: Expected "!", "(", ")", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "map", "null", "set", "true", "~", capitalized identifier, digit, or identifier
   5 | fun b(): Int {
 > 6 |     return a(,);
                    ^
@@ -233,7 +233,7 @@ exports[`should fail expr-fun-call-trailing-comma-no-args.fail 1`] = `
 `;
 
 exports[`should fail expr-method-call-trailing-comma-no-args.fail 1`] = `
-"<unknown>:2:24: Expected "!", "(", ")", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "null", "true", "~", capitalized identifier, digit, or identifier
+"<unknown>:2:24: Expected "!", "(", ")", "+", "-", "0", "\\"", "codeOf", "false", "initOf", "map", "null", "set", "true", "~", capitalized identifier, digit, or identifier
   1 | fun another() {
 > 2 |     return 42.toString(,);
                              ^
@@ -519,7 +519,7 @@ exports[`should fail ident-struct-cannot-start-with-__gen.fail 1`] = `
 `;
 
 exports[`should fail incomplete-return.fail 1`] = `
-"<unknown>:3:1: Expected "!", "(", "+", "-", "0", ";", "\\"", "codeOf", "false", "initOf", "null", "true", "}", "~", capitalized identifier, digit, or identifier
+"<unknown>:3:1: Expected "!", "(", "+", "-", "0", ";", "\\"", "codeOf", "false", "initOf", "map", "null", "set", "true", "}", "~", capitalized identifier, digit, or identifier
   2 |     return
 > 3 | 
       ^
diff --git a/src/grammar/grammar.peggy b/src/grammar/grammar.peggy
index fe2edd31..9c39edee 100644
--- a/src/grammar/grammar.peggy
+++ b/src/grammar/grammar.peggy
@@ -184,6 +184,9 @@ Bounced = "bounced";
 
 TypeId "capitalized identifier" = name:#$([A-Z] [a-zA-Z0-9_]*);
 
+typeArgs = generic<type>;
+generic<T> = "<" @commaList<T>? ">";
+
 statement
     = StatementLet
     / StatementDestruct
@@ -258,6 +261,8 @@ SuffixFieldAccess = "." name:Id;
 // Order is important
 primary
     = Parens
+    / MapLiteral
+    / SetLiteral
     / StructInstance
     / IntegerLiteral
     / BoolLiteral
@@ -274,6 +279,22 @@ Parens = child:parens;
 
 StructInstance = type:TypeId body:StructInstanceFields;
 StructInstanceFields = "{" fields:commaList<StructFieldInitializer>? "}";
+
+MapLiteral =
+    keyword<"map">
+    typeArgs:typeArgs
+    "{"
+    fields:commaList<mapField>?
+    "}";
+mapField = key:expression ":" value:expression;
+
+SetLiteral =
+    "set"
+    typeArgs:typeArgs
+    "{"
+    fields:commaList<expression>?
+    "}";
+
 InitOf = keyword<"initOf"> name:Id params:ParameterList<expression>;
 CodeOf = "codeOf" name:Id;
 
diff --git a/src/grammar/grammar.ts b/src/grammar/grammar.ts
index 90371ae4..9eae7cbf 100644
--- a/src/grammar/grammar.ts
+++ b/src/grammar/grammar.ts
@@ -204,6 +204,8 @@ export namespace $ast {
     readonly $: "TypeId";
     readonly name: string;
   }>;
+  export type generic<T> = commaList<T> | undefined;
+  export type typeArgs = generic<$type>;
   export type StatementLet = $.Located<{
     readonly $: "StatementLet";
     readonly name: Id;
@@ -348,6 +350,16 @@ export namespace $ast {
     readonly $: "Parens";
     readonly child: parens;
   }>;
+  export type MapLiteral = $.Located<{
+    readonly $: "MapLiteral";
+    readonly typeArgs: typeArgs;
+    readonly fields: commaList<mapField> | undefined;
+  }>;
+  export type SetLiteral = $.Located<{
+    readonly $: "SetLiteral";
+    readonly typeArgs: typeArgs;
+    readonly fields: commaList<expression> | undefined;
+  }>;
   export type StructInstance = $.Located<{
     readonly $: "StructInstance";
     readonly type: TypeId;
@@ -373,12 +385,16 @@ export namespace $ast {
   export type Null = $.Located<{
     readonly $: "Null";
   }>;
-  export type primary = Parens | StructInstance | IntegerLiteral | BoolLiteral | InitOf | CodeOf | Null | StringLiteral | Id;
+  export type primary = Parens | MapLiteral | SetLiteral | StructInstance | IntegerLiteral | BoolLiteral | InitOf | CodeOf | Null | StringLiteral | Id;
   export type parens = expression;
   export type StructInstanceFields = $.Located<{
     readonly $: "StructInstanceFields";
     readonly fields: commaList<StructFieldInitializer> | undefined;
   }>;
+  export type mapField = {
+    readonly key: expression;
+    readonly value: expression;
+  };
   export type StructFieldInitializer = $.Located<{
     readonly $: "StructFieldInitializer";
     readonly name: Id;
@@ -472,6 +488,8 @@ export const typePrimary: $.Parser<$ast.typePrimary> = $.alt(TypeGeneric, TypeRe
 export const MapKeyword: $.Parser<$ast.MapKeyword> = $.loc($.field($.pure("MapKeyword"), "$", $.right(keyword($.str("map")), $.eps)));
 export const Bounced: $.Parser<$ast.Bounced> = $.loc($.field($.pure("Bounced"), "$", $.right($.str("bounced"), $.eps)));
 export const TypeId: $.Parser<$ast.TypeId> = $.named("capitalized identifier", $.loc($.field($.pure("TypeId"), "$", $.field($.lex($.stry($.right($.regex<string>("A-Z", [$.ExpRange("A", "Z")]), $.right($.star($.regex<string | string | string | "_">("a-zA-Z0-9_", [$.ExpRange("a", "z"), $.ExpRange("A", "Z"), $.ExpRange("0", "9"), $.ExpString("_")])), $.eps)))), "name", $.eps))));
+export const generic = <T,>(T: $.Parser<T>): $.Parser<$ast.generic<T>> => $.right($.str("<"), $.left($.opt(commaList($.lazy(() => T))), $.str(">")));
+export const typeArgs: $.Parser<$ast.typeArgs> = generic($type);
 export const StatementLet: $.Parser<$ast.StatementLet> = $.loc($.field($.pure("StatementLet"), "$", $.right(keyword($.str("let")), $.field(Id, "name", $.field($.opt(ascription), "type", $.right($.str("="), $.field($.lazy(() => expression), "init", $.right(semicolon, $.eps))))))));
 export const StatementDestruct: $.Parser<$ast.StatementDestruct> = $.loc($.field($.pure("StatementDestruct"), "$", $.right(keyword($.str("let")), $.field(TypeId, "type", $.right($.str("{"), $.field(inter($.lazy(() => destructItem), $.str(",")), "fields", $.field($.lazy(() => optionalRest), "rest", $.right($.str("}"), $.right($.str("="), $.field($.lazy(() => expression), "init", $.right(semicolon, $.eps)))))))))));
 export const StatementBlock: $.Parser<$ast.StatementBlock> = $.loc($.field($.pure("StatementBlock"), "$", $.field($.lazy(() => statements), "body", $.eps)));
@@ -515,15 +533,18 @@ export const SuffixCall: $.Parser<$ast.SuffixCall> = $.loc($.field($.pure("Suffi
 export const SuffixFieldAccess: $.Parser<$ast.SuffixFieldAccess> = $.loc($.field($.pure("SuffixFieldAccess"), "$", $.right($.str("."), $.field(Id, "name", $.eps))));
 export const suffix: $.Parser<$ast.suffix> = $.alt(SuffixUnboxNotNull, $.alt(SuffixCall, SuffixFieldAccess));
 export const Parens: $.Parser<$ast.Parens> = $.loc($.field($.pure("Parens"), "$", $.field($.lazy(() => parens), "child", $.eps)));
+export const MapLiteral: $.Parser<$ast.MapLiteral> = $.loc($.field($.pure("MapLiteral"), "$", $.right(keyword($.str("map")), $.field(typeArgs, "typeArgs", $.right($.str("{"), $.field($.opt(commaList($.lazy(() => mapField))), "fields", $.right($.str("}"), $.eps)))))));
+export const SetLiteral: $.Parser<$ast.SetLiteral> = $.loc($.field($.pure("SetLiteral"), "$", $.right($.str("set"), $.field(typeArgs, "typeArgs", $.right($.str("{"), $.field($.opt(commaList(expression)), "fields", $.right($.str("}"), $.eps)))))));
 export const StructInstance: $.Parser<$ast.StructInstance> = $.loc($.field($.pure("StructInstance"), "$", $.field(TypeId, "type", $.field($.lazy(() => StructInstanceFields), "body", $.eps))));
 export const IntegerLiteral: $.Parser<$ast.IntegerLiteral> = $.loc($.field($.pure("IntegerLiteral"), "$", $.field($.alt($.lazy(() => IntegerLiteralHex), $.alt($.lazy(() => IntegerLiteralBin), $.alt($.lazy(() => IntegerLiteralOct), IntegerLiteralDec))), "value", $.eps)));
 export const BoolLiteral: $.Parser<$ast.BoolLiteral> = $.loc($.field($.pure("BoolLiteral"), "$", $.field($.alt($.str("true"), $.str("false")), "value", $.right($.lookNeg($.lazy(() => idPart)), $.eps))));
 export const InitOf: $.Parser<$ast.InitOf> = $.loc($.field($.pure("InitOf"), "$", $.right(keyword($.str("initOf")), $.field(Id, "name", $.field($.lazy(() => ParameterList(expression)), "params", $.eps)))));
 export const CodeOf: $.Parser<$ast.CodeOf> = $.loc($.field($.pure("CodeOf"), "$", $.right($.str("codeOf"), $.field(Id, "name", $.eps))));
 export const Null: $.Parser<$ast.Null> = $.loc($.field($.pure("Null"), "$", $.right(keyword($.str("null")), $.eps)));
-export const primary: $.Parser<$ast.primary> = $.alt(Parens, $.alt(StructInstance, $.alt(IntegerLiteral, $.alt(BoolLiteral, $.alt(InitOf, $.alt(CodeOf, $.alt(Null, $.alt(StringLiteral, Id))))))));
+export const primary: $.Parser<$ast.primary> = $.alt(Parens, $.alt(MapLiteral, $.alt(SetLiteral, $.alt(StructInstance, $.alt(IntegerLiteral, $.alt(BoolLiteral, $.alt(InitOf, $.alt(CodeOf, $.alt(Null, $.alt(StringLiteral, Id))))))))));
 export const parens: $.Parser<$ast.parens> = $.right($.str("("), $.left(expression, $.str(")")));
 export const StructInstanceFields: $.Parser<$ast.StructInstanceFields> = $.loc($.field($.pure("StructInstanceFields"), "$", $.right($.str("{"), $.field($.opt(commaList($.lazy(() => StructFieldInitializer))), "fields", $.right($.str("}"), $.eps)))));
+export const mapField: $.Parser<$ast.mapField> = $.field(expression, "key", $.right($.str(":"), $.field(expression, "value", $.eps)));
 export const StructFieldInitializer: $.Parser<$ast.StructFieldInitializer> = $.loc($.field($.pure("StructFieldInitializer"), "$", $.field(Id, "name", $.field($.opt($.right($.str(":"), expression)), "init", $.eps))));
 export const ParameterList = <T,>(T: $.Parser<T>): $.Parser<$ast.ParameterList<T>> => $.loc($.field($.pure("ParameterList"), "$", $.right($.str("("), $.field($.opt(commaList($.lazy(() => T))), "values", $.right($.str(")"), $.eps)))));
 export const IntegerLiteralHex: $.Parser<$ast.IntegerLiteralHex> = $.loc($.field($.pure("IntegerLiteralHex"), "$", $.field($.lex($.right($.str("0"), $.right($.regex<"x" | "X">("xX", [$.ExpString("x"), $.ExpString("X")]), $.lazy(() => underscored($.lazy(() => hexDigit)))))), "digits", $.eps)));
diff --git a/src/grammar/index.ts b/src/grammar/index.ts
index 220e8a99..b66a81bb 100644
--- a/src/grammar/index.ts
+++ b/src/grammar/index.ts
@@ -311,6 +311,39 @@ const parseStructInstance =
         );
     };
 
+const parseMapField =
+    ({ key, value }: $ast.mapField): Handler<Ast.MapField> =>
+    (ctx) => {
+        return {
+            key: parseExpression(key)(ctx),
+            value: parseExpression(value)(ctx),
+        };
+    };
+
+const parseMapLiteral =
+    ({
+        typeArgs,
+        fields,
+        loc,
+    }: $ast.MapLiteral): Handler<Ast.MapLiteral | Ast.Id> =>
+    (ctx) => {
+        const type = parseMapType(typeArgs, loc)(ctx);
+        if (type.kind === "type_id") {
+            return ctx.ast.Id("ERROR", loc);
+        }
+        return ctx.ast.MapLiteral(
+            type,
+            map(parseList(fields), parseMapField)(ctx),
+            loc,
+        );
+    };
+const parseSetLiteral =
+    ({ loc }: $ast.SetLiteral): Handler<Ast.Id> =>
+    (ctx) => {
+        ctx.err.noSetLiterals()(loc);
+        return ctx.ast.Id("ERROR", loc);
+    };
+
 const parseInitOf =
     ({ name, params, loc }: $ast.InitOf): Handler<Ast.InitOf> =>
     (ctx) => {
@@ -459,6 +492,8 @@ type Expression =
     | $ast.Suffix
     | $ast.Parens
     | $ast.StructInstance
+    | $ast.MapLiteral
+    | $ast.SetLiteral
     | $ast.IntegerLiteral
     | $ast.BoolLiteral
     | $ast.InitOf
@@ -475,6 +510,8 @@ const parseExpression: (input: Expression) => Handler<Ast.Expression> =
         Suffix: parseSuffix,
         Parens: parseParens,
         StructInstance: parseStructInstance,
+        MapLiteral: parseMapLiteral,
+        SetLiteral: parseSetLiteral,
         IntegerLiteral: parseIntegerLiteral,
         BoolLiteral: parseBoolLiteral,
         InitOf: parseInitOf,
@@ -841,6 +878,54 @@ const parseTypeId =
         return ctx.ast.TypeId(name, loc);
     };
 
+const parseMapType =
+    (
+        args: $ast.commaList<$ast.TypeAs> | undefined,
+        loc: $.Loc,
+    ): Handler<Ast.MapType | Ast.TypeId> =>
+    (ctx) => {
+        const parsedArgs = parseList(args);
+        const [key, value, ...rest] = parsedArgs;
+        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- eslint bug
+        if (!key || !value || rest.length > 0) {
+            ctx.err.genericArgCount("map", 2, parsedArgs.length)(loc);
+            return ctx.ast.TypeId("ERROR", loc);
+        }
+        const [keyAs, ...restKeyAs] = key.as;
+        if (restKeyAs.length > 0) {
+            ctx.err.mapOnlyOneAs("key")(loc);
+        }
+        if (key.type.optionals.length > 0) {
+            ctx.err.cannotBeOptional("map key types")(key.loc);
+        }
+        if (key.type.type.$ !== "TypeRegular") {
+            ctx.err.onlyTypeId("key")(loc);
+            return ctx.ast.TypeId("ERROR", loc);
+        }
+        const [valueAs, ...restValueAs] = value.as;
+        if (restValueAs.length > 0) {
+            ctx.err.mapOnlyOneAs("value")(loc);
+        }
+        if (value.type.optionals.length > 0) {
+            ctx.err.cannotBeOptional("map value types")(value.loc);
+        }
+        if (value.type.type.$ !== "TypeRegular") {
+            ctx.err.onlyTypeId("value")(loc);
+            return ctx.ast.TypeId("ERROR", loc);
+        }
+        const keyType = key.type.type.child;
+        const valueType = value.type.type.child;
+        return ctx.ast.MapType(
+            ctx.ast.TypeId(keyType.name, keyType.loc),
+            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- eslint bug
+            keyAs ? ctx.ast.Id(keyAs.name, keyAs.loc) : undefined,
+            ctx.ast.TypeId(valueType.name, valueType.loc),
+            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- eslint bug
+            valueAs ? ctx.ast.Id(valueAs.name, valueAs.loc) : undefined,
+            loc,
+        );
+    };
+
 const parseTypeOptional =
     ({ type, loc }: $ast.$type): Handler<Ast.Type> =>
     (ctx) => {
@@ -873,50 +958,7 @@ const parseTypeOptional =
         }
         const { name, args, loc: genericLoc } = innerType;
         if (name.$ === "MapKeyword") {
-            const parsedArgs = parseList(args);
-            const [key, value, ...rest] = parsedArgs;
-            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- eslint bug
-            if (!key || !value || rest.length > 0) {
-                ctx.err.genericArgCount(
-                    "map",
-                    2,
-                    parsedArgs.length,
-                )(genericLoc);
-                return ctx.ast.TypeId("ERROR", genericLoc);
-            }
-            const [keyAs, ...restKeyAs] = key.as;
-            if (restKeyAs.length > 0) {
-                ctx.err.mapOnlyOneAs("key")(genericLoc);
-            }
-            if (key.type.optionals.length > 0) {
-                ctx.err.cannotBeOptional("map key types")(key.loc);
-            }
-            if (key.type.type.$ !== "TypeRegular") {
-                ctx.err.onlyTypeId("key")(genericLoc);
-                return ctx.ast.TypeId("ERROR", genericLoc);
-            }
-            const [valueAs, ...restValueAs] = value.as;
-            if (restValueAs.length > 0) {
-                ctx.err.mapOnlyOneAs("value")(genericLoc);
-            }
-            if (value.type.optionals.length > 0) {
-                ctx.err.cannotBeOptional("map value types")(value.loc);
-            }
-            if (value.type.type.$ !== "TypeRegular") {
-                ctx.err.onlyTypeId("value")(genericLoc);
-                return ctx.ast.TypeId("ERROR", genericLoc);
-            }
-            const keyType = key.type.type.child;
-            const valueType = value.type.type.child;
-            return ctx.ast.MapType(
-                ctx.ast.TypeId(keyType.name, keyType.loc),
-                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- eslint bug
-                keyAs ? ctx.ast.Id(keyAs.name, keyAs.loc) : undefined,
-                ctx.ast.TypeId(valueType.name, valueType.loc),
-                // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- eslint bug
-                valueAs ? ctx.ast.Id(valueAs.name, valueAs.loc) : undefined,
-                genericLoc,
-            );
+            return parseMapType(args, genericLoc)(ctx);
         }
         if (name.$ === "Bounced") {
             const parsedArgs = parseList(args);
diff --git a/src/grammar/parser-error.ts b/src/grammar/parser-error.ts
index 0779fa15..639de480 100644
--- a/src/grammar/parser-error.ts
+++ b/src/grammar/parser-error.ts
@@ -178,6 +178,9 @@ export const syntaxErrorSchema = <T, U>(
                 sub`Assembly functions are only allowed at the module level - outside contracts or traits`,
             );
         },
+        noSetLiterals: () => {
+            return handle(text(`Set literals not supported yet`));
+        },
     };
 };
 
diff --git a/src/optimizer/interpreter.ts b/src/optimizer/interpreter.ts
index 9588534f..8561c045 100644
--- a/src/optimizer/interpreter.ts
+++ b/src/optimizer/interpreter.ts
@@ -1,4 +1,14 @@
-import { Address, beginCell, BitString, Cell, toNano } from "@ton/core";
+import {
+    Address,
+    beginCell,
+    BitString,
+    Cell,
+    Dictionary,
+    type DictionaryKey,
+    type DictionaryKeyTypes,
+    type DictionaryValue,
+    toNano,
+} from "@ton/core";
 import { paddedBufferToBits } from "@ton/core/dist/boc/utils/paddedBits";
 import { crc32 } from "@/utils/crc32";
 import type * as Ast from "@/ast/ast";
@@ -37,6 +47,12 @@ import {
 import { divFloor, modFloor } from "@/optimizer/util";
 import { sha256 } from "@/utils/sha256";
 import { prettyPrint } from "@/ast/ast-printer";
+import {
+    type MapSerializerDescrKey,
+    type MapSerializerDescrValue,
+    mapSerializers,
+} from "@/bindings/typescript/serializers";
+import { getMapAbi } from "@/types/resolveABITypeRef";
 
 // TVM integers are signed 257-bit integers
 const minTvmInt: bigint = -(2n ** 256n);
@@ -105,14 +121,14 @@ function ensureArgumentForEquality(val: Ast.Literal): Ast.Literal {
         case "string":
         case "slice":
             return val;
-        case "struct_value":
+        case "map_value":
+        case "struct_value": {
             throwErrorConstEval(
                 `struct ${prettyPrint(val)} cannot be an argument to == operator`,
                 val.loc,
             );
             break;
-        default:
-            throwInternalCompilerError("Unrecognized ast literal kind");
+        }
     }
 }
 
@@ -625,6 +641,48 @@ const defaultInterpreterConfig: InterpreterConfig = {
     maxLoopIterations: maxRepeatStatement,
 };
 
+type KeyExist = <R>(
+    cb: <K extends DictionaryKeyTypes>(
+        type: DictionaryKey<K>,
+        parse: (value: Ast.Literal, keyExprLoc: SrcInfo) => K,
+    ) => R,
+) => R;
+
+type ValueExist = <R>(
+    cb: <V>(
+        type: DictionaryValue<V>,
+        parse: (value: Ast.Literal, valueExprLoc: SrcInfo) => V,
+    ) => R,
+) => R;
+
+const expectNumber = (node: Ast.Literal, loc: SrcInfo): bigint => {
+    if (node.kind !== "number") {
+        return throwErrorConstEval("Unexpected expression type", loc);
+    }
+    return node.value;
+};
+
+const expectAddress = (node: Ast.Literal, loc: SrcInfo): Address => {
+    if (node.kind !== "address") {
+        return throwErrorConstEval("Unexpected expression type", loc);
+    }
+    return node.value;
+};
+
+const expectCell = (node: Ast.Literal, loc: SrcInfo): Cell => {
+    if (node.kind !== "cell") {
+        return throwErrorConstEval("Unexpected expression type", loc);
+    }
+    return node.value;
+};
+
+const expectBool = (node: Ast.Literal, loc: SrcInfo): boolean => {
+    if (node.kind !== "boolean") {
+        return throwErrorConstEval("Unexpected expression type", loc);
+    }
+    return node.value;
+};
+
 /*
 Interprets Tact AST trees.
 The constructor receives an optional CompilerContext which includes
@@ -851,8 +909,14 @@ export class Interpreter {
                 return this.interpretFieldAccess(ast);
             case "static_call":
                 return this.interpretStaticCall(ast);
-            default:
-                throwInternalCompilerError("Unrecognized expression kind");
+            case "map_value":
+                return this.interpretMapValue(ast);
+            case "map_literal":
+                return this.interpretMapLiteral(ast);
+            case "set_literal":
+                return throwInternalCompilerError(
+                    "Set literals are not supported",
+                );
         }
     }
 
@@ -1078,6 +1142,116 @@ export class Interpreter {
         // Struct values are already simplified to their simplest form
         return ast;
     }
+    private interpretMapValue(ast: Ast.MapValue): Ast.MapValue {
+        return ast;
+    }
+
+    private interpretMapLiteral(ast: Ast.MapLiteral): Ast.MapValue {
+        const keyTy = getType(this.context, ast.type.keyType);
+        const valTy = getType(this.context, ast.type.valueType);
+        const res = mapSerializers.abiMatcher(
+            getMapAbi(
+                {
+                    kind: "map",
+                    key: keyTy.name,
+                    keyAs:
+                        ast.type.keyStorageType !== undefined
+                            ? idText(ast.type.keyStorageType)
+                            : null,
+                    value: valTy.name,
+                    valueAs:
+                        ast.type.valueStorageType !== undefined
+                            ? idText(ast.type.valueStorageType)
+                            : null,
+                },
+                ast.loc,
+            ),
+        );
+        if (res === null) {
+            throwInternalCompilerError("Wrong map ABI");
+        }
+        const keyExist = this.getKeyParser(res.key);
+        const valueExist = this.getValueParser(res.value);
+        const bocHex = keyExist((keyType, parseKey) => {
+            return valueExist((valueType, parseValue) => {
+                if (ast.fields.length === 0) {
+                    return undefined;
+                }
+                let dict = Dictionary.empty(keyType, valueType);
+                for (const { key: keyExpr, value: valueExpr } of ast.fields) {
+                    const keyValue = parseKey(
+                        this.interpretExpressionInternal(keyExpr),
+                        keyExpr.loc,
+                    );
+                    const valValue = parseValue(
+                        this.interpretExpressionInternal(valueExpr),
+                        valueExpr.loc,
+                    );
+                    dict = dict.set(keyValue, valValue);
+                }
+                return beginCell()
+                    .storeDictDirect(dict)
+                    .endCell()
+                    .toBoc()
+                    .toString("hex");
+            });
+        });
+        return this.util.makeMapValue(bocHex, ast.type, ast.loc);
+    }
+
+    getKeyParser(src: MapSerializerDescrKey): KeyExist {
+        switch (src.kind) {
+            case "int": {
+                return (cb) =>
+                    cb(Dictionary.Keys.BigInt(src.bits), expectNumber);
+            }
+            case "uint": {
+                return (cb) =>
+                    cb(Dictionary.Keys.BigUint(src.bits), expectNumber);
+            }
+            case "address": {
+                return (cb) => cb(Dictionary.Keys.Address(), expectAddress);
+            }
+        }
+    }
+    getValueParser(src: MapSerializerDescrValue): ValueExist {
+        switch (src.kind) {
+            case "int": {
+                return (cb) =>
+                    cb(Dictionary.Values.BigInt(src.bits), expectNumber);
+            }
+            case "uint": {
+                return (cb) =>
+                    cb(Dictionary.Values.BigUint(src.bits), expectNumber);
+            }
+            case "varint": {
+                return (cb) =>
+                    cb(Dictionary.Values.BigVarInt(src.length), expectNumber);
+            }
+            case "varuint": {
+                return (cb) =>
+                    cb(Dictionary.Values.BigVarUint(src.length), expectNumber);
+            }
+            case "address": {
+                return (cb) => cb(Dictionary.Values.Address(), expectAddress);
+            }
+            case "cell": {
+                return (cb) => cb(Dictionary.Values.Cell(), expectCell);
+            }
+            case "boolean": {
+                return (cb) => cb(Dictionary.Values.Bool(), expectBool);
+            }
+            case "struct": {
+                return (cb) =>
+                    cb(Dictionary.Values.Cell(), (_, loc) => {
+                        throwNonFatalErrorConstEval(
+                            "Cannot evaluate map with struct values",
+                            loc,
+                        );
+                    });
+            }
+        }
+    }
 
     private interpretFieldAccess(ast: Ast.FieldAccess): Ast.Literal {
         // special case for contract/trait constant accesses via `self.constant`
diff --git a/src/pipeline/ast-factory-store.ts b/src/pipeline/ast-factory-store.ts
new file mode 100644
index 00000000..49b6ca0f
--- /dev/null
+++ b/src/pipeline/ast-factory-store.ts
@@ -0,0 +1,19 @@
+import { createContextStore, type CompilerContext } from "@/context/context";
+import type { FactoryAst } from "@/ast/ast-helpers";
+
+import { throwInternalCompilerError } from "@/error/errors";
+
+const astFactoryStore = createContextStore<FactoryAst>();
+const dummyFactoryStoreKey = "dummyFactoryStoreKey";
+
+export const setAstFactoryToStore = (ctx: CompilerContext, ast: FactoryAst) => {
+    astFactoryStore.set(ctx, dummyFactoryStoreKey, ast);
+};
+
+export const getAstFactoryFromStore = (ctx: CompilerContext): FactoryAst => {
+    const factory = astFactoryStore.get(ctx, dummyFactoryStoreKey);
+    if (factory === null) {
+        throwInternalCompilerError("Ast factory store was not set");
+    }
+    return factory;
+};
diff --git a/src/pipeline/precompile.ts b/src/pipeline/precompile.ts
index 1e06e09e..6f83fcdd 100644
--- a/src/pipeline/precompile.ts
+++ b/src/pipeline/precompile.ts
@@ -15,6 +15,7 @@ import { getAstFactory } from "@/ast/ast-helpers";
 import { getParser } from "@/grammar";
 import { evalComptimeExpressions } from "@/types/evalComptimeExpressions";
 import { computeReceiversEffects } from "@/types/effects";
+import { setAstFactoryToStore } from "@/pipeline/ast-factory-store";
 
 export function precompile(
     ctx: CompilerContext,
@@ -24,6 +25,8 @@ export function precompile(
     parsedModules?: Ast.Module[],
 ) {
     const ast = getAstFactory();
+    setAstFactoryToStore(ctx, ast);
+
     const parser = getParser(ast);
 
     // Load all sources
diff --git a/src/types/__snapshots__/resolveStatements.spec.ts.snap b/src/types/__snapshots__/resolveStatements.spec.ts.snap
index 749ead6e..d57bfe93 100644
--- a/src/types/__snapshots__/resolveStatements.spec.ts.snap
+++ b/src/types/__snapshots__/resolveStatements.spec.ts.snap
@@ -73,7 +73,7 @@ exports[`resolveStatements should fail statements for assign-const-struct-global
 `;
 
 exports[`resolveStatements should fail statements for bounced-type-is-smaller 1`] = `
-"<unknown>:23:22: Maximum size of the bounced message is 224 bytes, but the "c" field of type "A" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes
+"<unknown>:23:22: Maximum size of the bounced message is 224 bits, but the "c" field of type "A" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bits
   22 |     let y: Bool = src.b;
 > 23 |     let z: Int = src.c;
                             ^
@@ -846,6 +846,51 @@ exports[`resolveStatements should fail statements for map-deepEquals-type-mismat
 "
 `;
 
+exports[`resolveStatements should fail statements for map-literal-invalid-storage 1`] = `
+"<unknown>:6:42: "int640" is invalid as-annotation for map value type "Int"
+  5 |     get fun test3(): Int {
+> 6 |         let x = map<Int as int32, Int as int640> {};
+                                               ^~~~~~
+  7 |         return 0;
+"
+`;
+
+exports[`resolveStatements should fail statements for map-literal-invalid-type 1`] = `
+"<unknown>:7:21: Invalid map type. Check https://docs.tact-lang.org/book/maps#allowed-types
+  6 |     get fun test3(): Int {
+> 7 |         let x = map<Cell, Cell> {};
+                          ^~~~
+  8 |         return 0;
+"
+`;
+
+exports[`resolveStatements should fail statements for map-literal-mismatch-return 1`] = `
+"<unknown>:8:9: Type mismatch: "map<Address, Cell>" is not assignable to "map<Int as int32, Int as int32>"
+  7 |     get fun test1(): map<Int as int32, Int as int32> {
+> 8 |         return map<Address, Cell> {};
+              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  9 |     }
+"
+`;
+
+exports[`resolveStatements should fail statements for map-literal-mismatch-storage 1`] = `
+"<unknown>:6:9: Type mismatch: "map<Int as int32, Int as int64>" is not assignable to "map<Int as int64, Int as int32>"
+  5 |     get fun test3(): map<Int as int64, Int as int32> {
+> 6 |         return map<Int as int32, Int as int64> {};
+              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  7 |     }
+"
+`;
+
+exports[`resolveStatements should fail statements for map-literal-mismatch-var 1`] = `
+"<unknown>:8:9: Type mismatch: "map<Int as int32, Int as int32>" is not assignable to "map<Address, Cell>"
+  7 |     get fun test2(): Int {
+> 8 |         let x: map<Address, Cell> =
+              ^~~~~~~~~~~~~~~~~~~~~~~~~~~
+  9 |             map<Int as int32, Int as int32> {};
+"
+`;
+
 exports[`resolveStatements should fail statements for return-analysis-catch-if 1`] = `
 "<unknown>:4:1: Function does not always return a result. Adding 'return' statement(s) should fix the issue.
   3 | 
@@ -1306,7 +1351,7 @@ exports[`resolveStatements should fail statements for unknown-type-in-variable-d
 `;
 
 exports[`resolveStatements should fail statements for usage-of-bounced-field-in-type-that-is-too-big 1`] = `
-"<unknown>:14:29: Maximum size of the bounced message is 224 bytes, but the "amount" field of type "Withdraw" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes
+"<unknown>:14:29: Maximum size of the bounced message is 224 bits, but the "amount" field of type "Withdraw" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bits
   13 |         self.balance += msg.data; // ok
 > 14 |         self.balance += msg.amount;
                                    ^~~~~~
@@ -1315,7 +1360,7 @@ exports[`resolveStatements should fail statements for usage-of-bounced-field-in-
 `;
 
 exports[`resolveStatements should fail statements for usage-of-bounced-field-in-type-that-is-too-big-2 1`] = `
-"<unknown>:26:29: Maximum size of the bounced message is 224 bytes, but the "amount" field of type "Withdraw" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes
+"<unknown>:26:29: Maximum size of the bounced message is 224 bits, but the "amount" field of type "Withdraw" cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bits
   25 | 
 > 26 |         self.balance += msg.amount;
                                    ^~~~~~
@@ -1324,7 +1369,7 @@ exports[`resolveStatements should fail statements for usage-of-bounced-field-in-
 `;
 
 exports[`resolveStatements should fail statements for usage-of-bounced-field-that-is-too-big 1`] = `
-"<unknown>:12:29: Maximum size of the bounced message is 224 bytes, but the "amount" field of type "Withdraw" cannot fit into it because its too big, so it cannot be accessed. Reduce the type of this field so that it fits into 224 bytes
+"<unknown>:12:29: Maximum size of the bounced message is 224 bits, but the "amount" field of type "Withdraw" cannot fit into it because its too big, so it cannot be accessed. Reduce the type of this field so that it fits into 224 bits
   11 |     bounced(msg: bounced<Withdraw>){
 > 12 |         self.balance += msg.amount;
                                    ^~~~~~
diff --git a/src/types/effects.ts b/src/types/effects.ts
index bc4b5122..8206c667 100644
--- a/src/types/effects.ts
+++ b/src/types/effects.ts
@@ -212,6 +212,7 @@ function expressionEffects(
         case "address":
         case "cell":
         case "struct_value":
+        case "map_value":
         case "code_of": {
             return new Set<Effect>();
         }
@@ -269,6 +270,17 @@ function expressionEffects(
                 ),
             );
         }
+        case "map_literal": {
+            return unionAll(
+                expr.fields.flatMap(({ key, value }) => [
+                    expressionEffects(key, processedContractMethods, ctx),
+                    expressionEffects(value, processedContractMethods, ctx),
+                ]),
+            );
+        }
+        case "set_literal": {
+            throwInternalCompilerError("Set literals are not supported");
+        }
     }
 }
 
diff --git a/src/types/resolveABITypeRef.ts b/src/types/resolveABITypeRef.ts
index e3942bbb..87434d42 100644
--- a/src/types/resolveABITypeRef.ts
+++ b/src/types/resolveABITypeRef.ts
@@ -17,7 +17,7 @@ import {
     throwCompilationError,
     throwInternalCompilerError,
 } from "@/error/errors";
-import type { TypeRef } from "@/types/types";
+import type { TypeRef, TypeRefMap } from "@/types/types";
 import type { CompilerContext } from "@/context/context";
 import { getType } from "@/types/resolveDescriptors";
 import type { SrcInfo } from "@/grammar";
@@ -511,105 +511,103 @@ export function createABITypeRefFromTypeRef(
     }
 
     if (src.kind === "map") {
-        let key: string;
-        let keyFormat: string | number | undefined = undefined;
-        let value: string;
-        let valueFormat: string | number | undefined = undefined;
+        return getMapAbi(src, loc);
+    }
 
-        // Resolve key type
-        if (src.key === "Int") {
-            key = "int";
-            if (src.keyAs) {
-                const format = intMapKeyFormats[src.keyAs];
-                if (!format) {
-                    throwCompilationError(
-                        `Unsupported format ${src.keyAs} for map key`,
-                        loc,
-                    );
-                }
-                key = format.type;
-                keyFormat = format.format;
-            }
-        } else if (src.key === "Address") {
-            key = "address";
-            if (src.keyAs) {
+    if (src.kind === "ref_bounced") {
+        throwInternalCompilerError("Unexpected bounced reference");
+    }
+
+    throwInternalCompilerError(`Unsupported type`);
+}
+
+export function getMapAbi(src: TypeRefMap, loc: SrcInfo): ABITypeRef {
+    let key: string;
+    let keyFormat: string | number | undefined = undefined;
+    let value: string;
+    let valueFormat: string | number | undefined = undefined;
+
+    // Resolve key type
+    if (src.key === "Int") {
+        key = "int";
+        if (src.keyAs) {
+            const format = intMapKeyFormats[src.keyAs];
+            if (!format) {
                 throwCompilationError(
                     `Unsupported format ${src.keyAs} for map key`,
                     loc,
                 );
             }
-        } else {
-            throwInternalCompilerError(`Unsupported map key type "${src.key}"`);
+            key = format.type;
+            keyFormat = format.format;
+        }
+    } else if (src.key === "Address") {
+        key = "address";
+        if (src.keyAs) {
+            throwCompilationError(
+                `Unsupported format ${src.keyAs} for map key`,
+                loc,
+            );
         }
+    } else {
+        throwInternalCompilerError(`Unsupported map key type "${src.key}"`);
+    }
 
-        // Resolve value type
-        if (src.value === "Int") {
-            value = "int";
-            if (src.valueAs) {
-                const format = intMapValFormats[src.valueAs];
-                if (!format) {
-                    throwCompilationError(
-                        `Unsupported format ${src.valueAs} for map value`,
-                        loc,
-                    );
-                }
-                value = format.type;
-                valueFormat = format.format;
-            }
-        } else if (src.value === "Bool") {
-            value = "bool";
-            if (src.valueAs) {
+    // Resolve value type
+    if (src.value === "Int") {
+        value = "int";
+        if (src.valueAs) {
+            const format = intMapValFormats[src.valueAs];
+            if (!format) {
                 throwCompilationError(
                     `Unsupported format ${src.valueAs} for map value`,
                     loc,
                 );
             }
-        } else if (src.value === "Cell") {
-            value = "cell";
-            valueFormat = "ref";
-            if (src.valueAs && src.valueAs !== "reference") {
-                throwCompilationError(
-                    `Unsupported format ${src.valueAs} for map value`,
-                    loc,
-                );
-            }
-        } else if (src.value === "Slice") {
-            throwInternalCompilerError(
-                `Unsupported map value type "${src.value}"`,
+            value = format.type;
+            valueFormat = format.format;
+        }
+    } else if (src.value === "Bool") {
+        value = "bool";
+        if (src.valueAs) {
+            throwCompilationError(
+                `Unsupported format ${src.valueAs} for map value`,
+                loc,
             );
-        } else if (src.value === "Address") {
-            value = "address";
-            if (src.valueAs) {
-                throwCompilationError(
-                    `Unsupported format ${src.valueAs} for map value`,
-                    loc,
-                );
-            }
-        } else if (src.value === "String") {
-            throwInternalCompilerError(
-                `Unsupported map value type "${src.value}"`,
+        }
+    } else if (src.value === "Cell") {
+        value = "cell";
+        valueFormat = "ref";
+        if (src.valueAs && src.valueAs !== "reference") {
+            throwCompilationError(
+                `Unsupported format ${src.valueAs} for map value`,
+                loc,
+            );
+        }
+    } else if (src.value === "Slice") {
+        throwInternalCompilerError(`Unsupported map value type "${src.value}"`);
+    } else if (src.value === "Address") {
+        value = "address";
+        if (src.valueAs) {
+            throwCompilationError(
+                `Unsupported format ${src.valueAs} for map value`,
+                loc,
             );
-        } else if (src.value === "StringBuilder" || src.value === "Builder") {
-            throwInternalCompilerError(
-                `Unsupported map value type "${src.value}"`,
+        }
+    } else if (src.value === "String") {
+        throwInternalCompilerError(`Unsupported map value type "${src.value}"`);
+    } else if (src.value === "StringBuilder" || src.value === "Builder") {
+        throwInternalCompilerError(`Unsupported map value type "${src.value}"`);
+    } else {
+        value = src.value;
+        valueFormat = "ref";
+        if (src.valueAs && src.valueAs !== "reference") {
+            throwCompilationError(
+                `Unsupported format ${src.valueAs} for map value`,
+                loc,
             );
-        } else {
-            value = src.value;
-            valueFormat = "ref";
-            if (src.valueAs && src.valueAs !== "reference") {
-                throwCompilationError(
-                    `Unsupported format ${src.valueAs} for map value`,
-                    loc,
-                );
-            }
         }
-
-        return { kind: "dict", key, keyFormat, value, valueFormat };
     }
 
-    if (src.kind === "ref_bounced") {
-        throwInternalCompilerError("Unexpected bounced reference");
-    }
-
-    throwInternalCompilerError(`Unsupported type`);
+    return { kind: "dict", key, keyFormat, value, valueFormat };
 }
diff --git a/src/types/resolveDescriptors.ts b/src/types/resolveDescriptors.ts
index 56afeec0..2c4474ad 100644
--- a/src/types/resolveDescriptors.ts
+++ b/src/types/resolveDescriptors.ts
@@ -109,7 +109,7 @@ function verifyMapTypes(
     verifyMapAsAnnotationsForPrimitiveTypes(typeId, asAnnotation, kind);
 }
 
-function verifyMapType(mapTy: Ast.MapType, isValTypeStruct: boolean) {
+export function verifyMapType(mapTy: Ast.MapType, isValTypeStruct: boolean) {
     // optional and other compound key and value types are disallowed at the level of grammar
 
     // check allowed key types
diff --git a/src/types/resolveExpression.ts b/src/types/resolveExpression.ts
index 0448cc79..d3797e9e 100644
--- a/src/types/resolveExpression.ts
+++ b/src/types/resolveExpression.ts
@@ -14,6 +14,8 @@ import {
     getType,
     hasStaticConstant,
     hasStaticFunction,
+    resolveTypeRef,
+    verifyMapType,
 } from "@/types/resolveDescriptors";
 import type { FunctionParameter, TypeRef } from "@/types/types";
 import { printTypeRef, typeRefEquals } from "@/types/types";
@@ -206,6 +208,51 @@ function resolveStructNew(
     });
 }
 
+function resolveMapLiteral(
+    exp: Ast.MapLiteral,
+    sctx: StatementContext,
+    ctx: CompilerContext,
+): CompilerContext {
+    const keyTy = getType(ctx, exp.type.keyType);
+    const valTy = getType(ctx, exp.type.valueType);
+    verifyMapType(exp.type, valTy.kind === "struct");
+    const keyFormalType = resolveTypeRef(ctx, exp.type.keyType);
+    const valueFormalType = resolveTypeRef(ctx, exp.type.valueType);
+
+    for (const { key, value } of exp.fields) {
+        ctx = resolveExpression(key, sctx, ctx);
+        ctx = resolveExpression(value, sctx, ctx);
+        const keyType = getExpType(ctx, key);
+        const valueType = getExpType(ctx, value);
+        if (!isAssignable(keyType, keyFormalType)) {
+            throwCompilationError(
+                `Key of type "${printTypeRef(keyType)}" cannot be used as a key for map with keys of type "${printTypeRef(keyFormalType)}"`,
+                key.loc,
+            );
+        }
+        if (!isAssignable(valueType, valueFormalType)) {
+            throwCompilationError(
+                `Value of type "${printTypeRef(valueType)}" cannot be used as a value for map with values of type "${printTypeRef(valueFormalType)}"`,
+                key.loc,
+            );
+        }
+    }
+
+    return registerExpType(ctx, exp, {
+        kind: "map",
+        key: keyTy.name,
+        keyAs:
+            exp.type.keyStorageType !== undefined
+                ? idText(exp.type.keyStorageType)
+                : null,
+        value: valTy.name,
+        valueAs:
+            exp.type.valueStorageType !== undefined
+                ? idText(exp.type.valueStorageType)
+                : null,
+    });
+}
+
 function resolveBinaryOp(
     exp: Ast.OpBinary,
     sctx: StatementContext,
@@ -449,7 +496,7 @@ function resolveFieldAccess(
     const fieldIndex = srcT.fields.findIndex((v) => eqNames(v.name, exp.field));
     const field = fieldIndex !== -1 ? srcT.fields[fieldIndex] : undefined;
 
-    // If we found a field of bounced<T>, check if the field doesn't fit in 224 bytes and cannot be accessed
+    // If we found a field of bounced<T>, check if the field doesn't fit in 224 bits and cannot be accessed
     if (
         src.kind === "ref_bounced" &&
         field &&
@@ -457,13 +504,13 @@ function resolveFieldAccess(
     ) {
         if (srcT.fields.length === 1) {
             throwCompilationError(
-                `Maximum size of the bounced message is 224 bytes, but the ${idTextErr(exp.field)} field of type ${idTextErr(src.name)} cannot fit into it because its too big, so it cannot be accessed. Reduce the type of this field so that it fits into 224 bytes`,
+                `Maximum size of the bounced message is 224 bits, but the ${idTextErr(exp.field)} field of type ${idTextErr(src.name)} cannot fit into it because its too big, so it cannot be accessed. Reduce the type of this field so that it fits into 224 bits`,
                 exp.field.loc,
             );
         }
 
         throwCompilationError(
-            `Maximum size of the bounced message is 224 bytes, but the ${idTextErr(exp.field)} field of type ${idTextErr(src.name)} cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bytes`,
+            `Maximum size of the bounced message is 224 bits, but the ${idTextErr(exp.field)} field of type ${idTextErr(src.name)} cannot fit into it due to the size of previous fields or its own size, so it cannot be accessed. Make the type of the fields before this one smaller, or reduce the type of this field so that it fits into 224 bits`,
             exp.field.loc,
         );
     }
@@ -873,6 +920,17 @@ export function resolveExpression(
         case "struct_instance": {
             return resolveStructNew(exp, sctx, ctx);
         }
+        case "map_literal": {
+            return resolveMapLiteral(exp, sctx, ctx);
+        }
+        case "map_value": {
+            return throwInternalCompilerError(
+                "Map value should never get here",
+            );
+        }
+        case "set_literal": {
+            return throwInternalCompilerError("Set literals are not supported");
+        }
         case "op_binary": {
             return resolveBinaryOp(exp, sctx, ctx);
         }
diff --git a/src/types/stmts-failed/map-literal-invalid-storage.tact b/src/types/stmts-failed/map-literal-invalid-storage.tact
new file mode 100644
index 00000000..cc0b4375
--- /dev/null
+++ b/src/types/stmts-failed/map-literal-invalid-storage.tact
@@ -0,0 +1,9 @@
+primitive Int;
+trait BaseTrait {}
+
+contract MapLiteralMismatch {
+    get fun test3(): Int {
+        let x = map<Int as int32, Int as int640> {};
+        return 0;
+    }
+}
diff --git a/src/types/stmts-failed/map-literal-invalid-type.tact b/src/types/stmts-failed/map-literal-invalid-type.tact
new file mode 100644
index 00000000..5ce1b289
--- /dev/null
+++ b/src/types/stmts-failed/map-literal-invalid-type.tact
@@ -0,0 +1,10 @@
+primitive Int;
+primitive Cell;
+trait BaseTrait {}
+
+contract MapLiteralMismatch {
+    get fun test3(): Int {
+        let x = map<Cell, Cell> {};
+        return 0;
+    }
+}
diff --git a/src/types/stmts-failed/map-literal-mismatch-return.tact b/src/types/stmts-failed/map-literal-mismatch-return.tact
new file mode 100644
index 00000000..8d5ef4e7
--- /dev/null
+++ b/src/types/stmts-failed/map-literal-mismatch-return.tact
@@ -0,0 +1,10 @@
+primitive Int;
+primitive Cell;
+primitive Address;
+trait BaseTrait {}
+
+contract MapLiteralMismatch {
+    get fun test1(): map<Int as int32, Int as int32> {
+        return map<Address, Cell> {};
+    }
+}
diff --git a/src/types/stmts-failed/map-literal-mismatch-storage.tact b/src/types/stmts-failed/map-literal-mismatch-storage.tact
new file mode 100644
index 00000000..235a45b0
--- /dev/null
+++ b/src/types/stmts-failed/map-literal-mismatch-storage.tact
@@ -0,0 +1,8 @@
+primitive Int;
+trait BaseTrait {}
+
+contract MapLiteralMismatch {
+    get fun test3(): map<Int as int64, Int as int32> {
+        return map<Int as int32, Int as int64> {};
+    }
+}
diff --git a/src/types/stmts-failed/map-literal-mismatch-var.tact b/src/types/stmts-failed/map-literal-mismatch-var.tact
new file mode 100644
index 00000000..27a7130c
--- /dev/null
+++ b/src/types/stmts-failed/map-literal-mismatch-var.tact
@@ -0,0 +1,12 @@
+primitive Int;
+primitive Cell;
+primitive Address;
+trait BaseTrait {}
+
+contract MapLiteralMismatch {
+    get fun test2(): Int {
+        let x: map<Address, Cell> =
+            map<Int as int32, Int as int32> {};
+        return 0;
+    }
+}
diff --git a/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact b/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact
index 1632e8ec..0a16e3c0 100644
--- a/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact
+++ b/src/types/stmts-failed/usage-of-bounced-field-in-type-that-is-too-big.tact
@@ -3,7 +3,7 @@ trait BaseTrait { }
 
 message Withdraw {
     data: Int as uint128;
-    amount: Int as uint128; // exceeds 224 bytes
+    amount: Int as uint128; // exceeds 224 bits
 }
 
 contract Fund {
diff --git a/src/types/types.ts b/src/types/types.ts
index ed5e1e31..477157aa 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -25,29 +25,39 @@ export type TypeDescription = {
     constants: ConstantDescription[];
 };
 
+export type TypeRefRef = {
+    kind: "ref";
+    name: string;
+    optional: boolean;
+};
+
+export type TypeRefMap = {
+    kind: "map";
+    key: string;
+    keyAs: string | null;
+    value: string;
+    valueAs: string | null;
+};
+
+export type TypeRefBounced = {
+    kind: "ref_bounced";
+    name: string;
+};
+
+export type TypeRefVoid = {
+    kind: "void";
+};
+
+export type TypeRefNull = {
+    kind: "null";
+};
+
 export type TypeRef =
-    | {
-          kind: "ref";
-          name: string;
-          optional: boolean;
-      }
-    | {
-          kind: "map";
-          key: string;
-          keyAs: string | null;
-          value: string;
-          valueAs: string | null;
-      }
-    | {
-          kind: "ref_bounced";
-          name: string;
-      }
-    | {
-          kind: "void";
-      }
-    | {
-          kind: "null";
-      };
+    | TypeRefRef
+    | TypeRefMap
+    | TypeRefBounced
+    | TypeRefVoid
+    | TypeRefNull;
 
 export type FieldDescription = {
     name: string;



Test patch:
diff --git a/src/fmt/test/formatter-test.spec.ts b/src/fmt/test/formatter-test.spec.ts
index 67af7cdb..d9adf550 100644
--- a/src/fmt/test/formatter-test.spec.ts
+++ b/src/fmt/test/formatter-test.spec.ts
@@ -317,19 +317,12 @@ describe("should format", () => {
     );
 
     it(
-        "assign statement with augmented operator with space",
-        test(
-            `
-        fun foo() {
-            a << = 100;
-        }
-    `,
-            `
+        "augmented assign",
+        intact(`
         fun foo() {
-            a <<= 100;
+            foo += 10;
         }
-    `,
-        ),
+    `),
     );
 
     it(
@@ -1698,6 +1691,113 @@ describe("should format", () => {
             `),
             );
         });
+
+        describe("map literals", () => {
+            it(
+                "empty map literal",
+                intact(`
+                    fun foo() {
+                        map<Int, Int> {};
+                    }
+                `),
+            );
+
+            it(
+                "empty map literal with as types",
+                intact(`
+                    fun foo() {
+                        map<Int as coins, Int as uint8> {};
+                    }
+                `),
+            );
+
+            it(
+                "map literal with single inline entry",
+                intact(`
+                    fun foo() {
+                        map<Int, Int> { 10: 20 };
+                    }
+                `),
+            );
+
+            it(
+                "map literal with single inline entry with comment",
+                intact(`
+                    fun foo() {
+                        map<Int, Int> { 10: 20 /* some comment */ };
+                    }
+                `),
+            );
+
+            it(
+                "map literal with several entries",
+                intact(`
+                    fun foo() {
+                        map<Int, Int> {
+                            10: 20,
+                            30: 40,
+                        };
+                    }
+                `),
+            );
+
+            it(
+                "map literal with several entries and comment",
+                intact(`
+                    fun foo() {
+                        map<Int, Int> {
+                            // comment
+                            10: 20,
+                            30: 40,
+                        };
+                    }
+                `),
+            );
+
+            it(
+                "map literal with several entries and multiline value",
+                intact(`
+                    fun foo() {
+                        map<Int, Cell> {
+                            10: beginCell()
+                                .storeUint()
+                                .endCell(),
+                            30: beginCell().endCell(),
+                        };
+                    }
+                `),
+            );
+
+            it(
+                "map literal with several entries and struct instances",
+                intact(`
+                    fun foo() {
+                        map<Int as uint16, Foo> {
+                            1: Foo { x: 1, y: 2 },
+                            2: Foo { x: 0, y: 1 },
+                        };
+                    }
+                `),
+            );
+
+            it(
+                "map literal with several entries and multiline struct instances",
+                intact(`
+                    fun foo() {
+                        map<Int as uint16, Foo> {
+                            1: Foo {
+                                x: 1,
+                                y: 2,
+                            },
+                            2: Foo {
+                                x: 0,
+                                y: 1,
+                            },
+                        };
+                    }
+                `),
+            );
+        });
     });
 
     describe("imports", () => {
diff --git a/src/test/compilation-failed/contracts/map-literal-cell.tact b/src/test/compilation-failed/contracts/map-literal-cell.tact
new file mode 100644
index 00000000..2fcec2f3
--- /dev/null
+++ b/src/test/compilation-failed/contracts/map-literal-cell.tact
@@ -0,0 +1,8 @@
+contract MapLiteralCell {
+    get fun cell(): map<Int as uint4, Cell> {
+        return map<Int as uint4, Cell> {
+            1: emptyCell(),
+            2: beginCell().storeUint(1, 1).endCell(),
+        };
+    }
+}
diff --git a/src/test/compilation-failed/contracts/map-literal-runtime.tact b/src/test/compilation-failed/contracts/map-literal-runtime.tact
new file mode 100644
index 00000000..c9035ea0
--- /dev/null
+++ b/src/test/compilation-failed/contracts/map-literal-runtime.tact
@@ -0,0 +1,8 @@
+contract MapLiteralRuntime {
+    get fun runtime(x: Int): map<Int as uint4, Int as uint4> {
+        return map<Int as uint4, Int as uint4> {
+            1: x,
+            2: x,
+        };
+    }
+}
diff --git a/src/test/compilation-failed/contracts/map-literal-structs.tact b/src/test/compilation-failed/contracts/map-literal-structs.tact
new file mode 100644
index 00000000..f8f59d8f
--- /dev/null
+++ b/src/test/compilation-failed/contracts/map-literal-structs.tact
@@ -0,0 +1,8 @@
+contract MapLiteralStructs {
+    get fun structs(): map<Int as uint16, Foo> {
+        return map<Int as uint16, Foo> {
+            1: Foo { x: 1, y: 2 },
+            2: Foo { x: 0, y: 1 },
+        };
+    }
+}
diff --git a/src/test/e2e-emulated/maps/__snapshots__/map-literals.spec.ts.snap b/src/test/e2e-emulated/maps/__snapshots__/map-literals.spec.ts.snap
new file mode 100644
index 00000000..56440497
--- /dev/null
+++ b/src/test/e2e-emulated/maps/__snapshots__/map-literals.spec.ts.snap
@@ -0,0 +1,325 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`should correctly compile const in contract 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 16,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {
+    "n:-1" => 2,
+  },
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`should correctly compile empty literals 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 16,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {},
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`should correctly compile global const 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 16,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {
+    "n:1" => -2,
+  },
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`should correctly compile in functions 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 16,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {
+    "n:1234" => 2345,
+    "n:8765" => 7654,
+  },
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`should create maps with address keys 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 267,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {
+    "a:EQAYqo4u7VF0fa4DPAebk4g9lBytj2VFny7pzXR0trjtXQaO" => 2000000000n,
+    "a:EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2" => 1000000000n,
+  },
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`should work anonymously 1`] = `3n`;
+
+exports[`should work as global constant map 1`] = `-2n`;
+
+exports[`should work with expressions in keys and values 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 16,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {
+    "n:3" => 7,
+  },
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`should work with large maps 1`] = `
+Dictionary {
+  "_key": {
+    "bits": 257,
+    "parse": [Function],
+    "serialize": [Function],
+  },
+  "_map": Map {
+    "b:0" => 0n,
+    "b:1" => 1n,
+    "b:2" => 2n,
+    "b:3" => 3n,
+    "b:4" => 4n,
+    "b:5" => 5n,
+    "b:6" => 6n,
+    "b:7" => 7n,
+    "b:8" => 8n,
+    "b:9" => 9n,
+    "b:10" => 10n,
+    "b:11" => 11n,
+    "b:12" => 12n,
+    "b:13" => 13n,
+    "b:14" => 14n,
+    "b:15" => 15n,
+    "b:16" => 16n,
+    "b:17" => 17n,
+    "b:18" => 18n,
+    "b:19" => 19n,
+    "b:20" => 20n,
+    "b:21" => 21n,
+    "b:22" => 22n,
+    "b:23" => 23n,
+    "b:24" => 24n,
+    "b:25" => 25n,
+    "b:26" => 26n,
+    "b:27" => 27n,
+    "b:28" => 28n,
+    "b:29" => 29n,
+    "b:30" => 30n,
+    "b:31" => 31n,
+    "b:32" => 32n,
+    "b:33" => 33n,
+    "b:34" => 34n,
+    "b:35" => 35n,
+    "b:36" => 36n,
+    "b:37" => 37n,
+    "b:38" => 38n,
+    "b:39" => 39n,
+    "b:40" => 40n,
+    "b:41" => 41n,
+    "b:42" => 42n,
+    "b:43" => 43n,
+    "b:44" => 44n,
+    "b:45" => 45n,
+    "b:46" => 46n,
+    "b:47" => 47n,
+    "b:48" => 48n,
+    "b:49" => 49n,
+    "b:50" => 50n,
+    "b:51" => 51n,
+    "b:52" => 52n,
+    "b:53" => 53n,
+    "b:54" => 54n,
+    "b:55" => 55n,
+    "b:56" => 56n,
+    "b:57" => 57n,
+    "b:58" => 58n,
+    "b:59" => 59n,
+    "b:60" => 60n,
+    "b:61" => 61n,
+    "b:62" => 62n,
+    "b:63" => 63n,
+    "b:64" => 64n,
+    "b:65" => 65n,
+    "b:66" => 66n,
+    "b:67" => 67n,
+    "b:68" => 68n,
+    "b:69" => 69n,
+    "b:70" => 70n,
+    "b:71" => 71n,
+    "b:72" => 72n,
+    "b:73" => 73n,
+    "b:74" => 74n,
+    "b:75" => 75n,
+    "b:76" => 76n,
+    "b:77" => 77n,
+    "b:78" => 78n,
+    "b:79" => 79n,
+    "b:80" => 80n,
+    "b:81" => 81n,
+    "b:82" => 82n,
+    "b:83" => 83n,
+    "b:84" => 84n,
+    "b:85" => 85n,
+    "b:86" => 86n,
+    "b:87" => 87n,
+    "b:88" => 88n,
+    "b:89" => 89n,
+    "b:90" => 90n,
+    "b:91" => 91n,
+    "b:92" => 92n,
+    "b:93" => 93n,
+    "b:94" => 94n,
+    "b:95" => 95n,
+    "b:96" => 96n,
+    "b:97" => 97n,
+    "b:98" => 98n,
+    "b:99" => 99n,
+    "b:100" => 100n,
+    "b:101" => 101n,
+    "b:102" => 102n,
+    "b:103" => 103n,
+    "b:104" => 104n,
+    "b:105" => 105n,
+    "b:106" => 106n,
+    "b:107" => 107n,
+    "b:108" => 108n,
+    "b:109" => 109n,
+    "b:110" => 110n,
+    "b:111" => 111n,
+    "b:112" => 112n,
+    "b:113" => 113n,
+    "b:114" => 114n,
+    "b:115" => 115n,
+    "b:116" => 116n,
+    "b:117" => 117n,
+    "b:118" => 118n,
+    "b:119" => 119n,
+    "b:120" => 120n,
+    "b:121" => 121n,
+    "b:122" => 122n,
+    "b:123" => 123n,
+    "b:124" => 124n,
+    "b:125" => 125n,
+    "b:126" => 126n,
+    "b:127" => 127n,
+    "b:128" => 128n,
+    "b:129" => 129n,
+    "b:130" => 130n,
+    "b:131" => 131n,
+    "b:132" => 132n,
+    "b:133" => 133n,
+    "b:134" => 134n,
+    "b:135" => 135n,
+    "b:136" => 136n,
+    "b:137" => 137n,
+    "b:138" => 138n,
+    "b:139" => 139n,
+    "b:140" => 140n,
+    "b:141" => 141n,
+    "b:142" => 142n,
+    "b:143" => 143n,
+    "b:144" => 144n,
+    "b:145" => 145n,
+    "b:146" => 146n,
+    "b:147" => 147n,
+    "b:148" => 148n,
+    "b:149" => 149n,
+    "b:150" => 150n,
+    "b:151" => 151n,
+    "b:152" => 152n,
+    "b:153" => 153n,
+    "b:154" => 154n,
+    "b:155" => 155n,
+    "b:156" => 156n,
+    "b:157" => 157n,
+    "b:158" => 158n,
+    "b:159" => 159n,
+    "b:160" => 160n,
+    "b:161" => 161n,
+    "b:162" => 162n,
+    "b:163" => 163n,
+    "b:164" => 164n,
+    "b:165" => 165n,
+    "b:166" => 166n,
+    "b:167" => 167n,
+    "b:168" => 168n,
+    "b:169" => 169n,
+    "b:170" => 170n,
+    "b:171" => 171n,
+    "b:172" => 172n,
+    "b:173" => 173n,
+    "b:174" => 174n,
+    "b:175" => 175n,
+    "b:176" => 176n,
+    "b:177" => 177n,
+    "b:178" => 178n,
+    "b:179" => 179n,
+    "b:180" => 180n,
+    "b:181" => 181n,
+    "b:182" => 182n,
+    "b:183" => 183n,
+    "b:184" => 184n,
+    "b:185" => 185n,
+    "b:186" => 186n,
+    "b:187" => 187n,
+    "b:188" => 188n,
+    "b:189" => 189n,
+    "b:190" => 190n,
+    "b:191" => 191n,
+    "b:192" => 192n,
+    "b:193" => 193n,
+    "b:194" => 194n,
+    "b:195" => 195n,
+    "b:196" => 196n,
+    "b:197" => 197n,
+    "b:198" => 198n,
+    "b:199" => 199n,
+  },
+  "_value": {
+    "parse": [Function],
+    "serialize": [Function],
+  },
+}
+`;
+
+exports[`the last value of the same key should be used 1`] = `4n`;
diff --git a/src/test/e2e-emulated/maps/map-literals.spec.ts b/src/test/e2e-emulated/maps/map-literals.spec.ts
new file mode 100644
index 00000000..5e39a9e3
--- /dev/null
+++ b/src/test/e2e-emulated/maps/map-literals.spec.ts
@@ -0,0 +1,120 @@
+import "@ton/test-utils";
+
+import { toNano } from "@ton/core";
+import { Blockchain } from "@ton/sandbox";
+import { Flat } from "./output/map-literals_Flat";
+
+import { setStoragePrices } from "@/test/utils/gasUtils";
+
+const deployValue = toNano("1"); // `dump` is expensive
+
+const setup = async () => {
+    const blockchain = await Blockchain.create();
+    blockchain.verbosity.print = false;
+
+    const config = blockchain.config;
+
+    blockchain.setConfig(
+        setStoragePrices(config, {
+            unixTimeSince: 0,
+            bitPricePerSecond: 0n,
+            cellPricePerSecond: 0n,
+            masterChainBitPricePerSecond: 0n,
+            masterChainCellPricePerSecond: 0n,
+        }),
+    );
+
+    const treasury = await blockchain.treasury("treasury");
+
+    const contract = blockchain.openContract(await Flat.fromInit());
+
+    const deployResult = await contract.send(
+        treasury.getSender(),
+        { value: deployValue },
+        null,
+    );
+    expect(deployResult.transactions).toHaveTransaction({
+        from: treasury.address,
+        to: contract.address,
+        success: true,
+        deploy: true,
+    });
+
+    return {
+        blockchain,
+        treasury,
+        contract,
+    };
+};
+
+it("should correctly compile in functions", async () => {
+    const { contract } = await setup();
+    const result = await contract.getInState();
+    expect(result).toMatchSnapshot();
+});
+
+it("should correctly compile empty literals", async () => {
+    const { contract } = await setup();
+    const result = await contract.getEmpty();
+    expect(result).toMatchSnapshot();
+});
+
+it("should correctly compile const in contract", async () => {
+    const { contract } = await setup();
+    const result = await contract.getContractConst();
+    expect(result).toMatchSnapshot();
+});
+
+it("should correctly compile global const", async () => {
+    const { contract } = await setup();
+    const result = await contract.getGlobalConst();
+    expect(result).toMatchSnapshot();
+});
+
+it("should create maps with address keys", async () => {
+    const { contract } = await setup();
+    const result = await contract.getAddress();
+    expect(result).toMatchSnapshot();
+});
+
+it("should work with large maps", async () => {
+    const { contract } = await setup();
+    const result = await contract.getLarge();
+    expect(result).toMatchSnapshot();
+});
+
+it("should match explicitly constructed maps", async () => {
+    const { contract } = await setup();
+    const result = await contract.getEq();
+    expect(result).toBeTruthy();
+});
+
+it("should work with expressions in keys and values", async () => {
+    const { contract } = await setup();
+    const result = await contract.getExpression();
+    expect(result).toMatchSnapshot();
+});
+
+it("should keep get/set invariants of regular maps", async () => {
+    const { contract } = await setup();
+    const result = await contract.getGetSetResult();
+    expect(result).toBeTruthy();
+});
+
+it("should work as global constant map", async () => {
+    const { contract } = await setup();
+    const result = await contract.getDecode();
+    expect(result).toMatchSnapshot();
+});
+
+it("should work anonymously", async () => {
+    const { contract } = await setup();
+    const result = await contract.getAnonymous();
+    expect(result).toMatchSnapshot();
+});
+
+it("the last value of the same key should be used", async () => {
+    const { contract } = await setup();
+    const result = await contract.getDuplicates();
+    expect(result).toMatchSnapshot();
+});
diff --git a/src/test/e2e-emulated/maps/map-literals.tact b/src/test/e2e-emulated/maps/map-literals.tact
new file mode 100644
index 00000000..03a12216
--- /dev/null
+++ b/src/test/e2e-emulated/maps/map-literals.tact
@@ -0,0 +1,100 @@
+const globalConst: map<Int as uint16, Int as int32> =
+    map<Int as uint16, Int as int32> { 1: -2 };
+
+struct Foo {
+    x: Int as uint2;
+    y: Int as uint2;
+}
+
+fun decode(key: Int): Int {
+    return globalConst.get(key)!!;
+}
+
+contract Flat {
+    x: map<Int as uint16, Int as int32>;
+
+    receive() {
+        self.x = map<Int as uint16, Int as int32> {
+            1234: 2345,
+            8765: 7654,
+        };
+    }
+
+    get fun inState(): map<Int as uint16, Int as int32> {
+        return self.x;
+    }
+
+    get fun empty(): map<Int as uint16, Int as int32> {
+        return map<Int as uint16, Int as int32> {};
+    }
+
+    const contractConst: map<Int as int16, Int as uint32> =
+        map<Int as int16, Int as uint32> { -1: 2 };
+
+    get fun contractConst(): map<Int as int16, Int as uint32> {
+        return self.contractConst;
+    }
+
+    get fun globalConst(): map<Int as uint16, Int as int32> {
+        return globalConst;
+    }
+
+    get fun address(): map<Address, Int as coins> {
+        return map<Address, Int as coins> {
+            address("EQDtFpEwcFAEcRe5mLVh2N6C0x-_hJEM7W61_JLnSF74p4q2"): ton("1"),
+            address("EQAYqo4u7VF0fa4DPAebk4g9lBytj2VFny7pzXR0trjtXQaO"): ton("2"),
+        };
+    }
+
+    get fun large(): map<Int as int257, Int as int257> {
+        return map<Int as int257, Int as int257> {
+            // fmt-ignore
+            0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99, 100: 100, 101: 101, 102: 102, 103: 103, 104: 104, 105: 105, 106: 106, 107: 107, 108: 108, 109: 109, 110: 110, 111: 111, 112: 112, 113: 113, 114: 114, 115: 115, 116: 116, 117: 117, 118: 118, 119: 119, 120: 120, 121: 121, 122: 122, 123: 123, 124: 124, 125: 125, 126: 126, 127: 127, 128: 128, 129: 129, 130: 130, 131: 131, 132: 132, 133: 133, 134: 134, 135: 135, 136: 136, 137: 137, 138: 138, 139: 139, 140: 140, 141: 141, 142: 142, 143: 143, 144: 144, 145: 145, 146: 146, 147: 147, 148: 148, 149: 149, 150: 150, 151: 151, 152: 152, 153: 153, 154: 154, 155: 155, 156: 156, 157: 157, 158: 158, 159: 159, 160: 160, 161: 161, 162: 162, 163: 163, 164: 164, 165: 165, 166: 166, 167: 167, 168: 168, 169: 169, 170: 170, 171: 171, 172: 172, 173: 173, 174: 174, 175: 175, 176: 176, 177: 177, 178: 178, 179: 179, 180: 180, 181: 181, 182: 182, 183: 183, 184: 184, 185: 185, 186: 186, 187: 187, 188: 188, 189: 189, 190: 190, 191: 191, 192: 192, 193: 193, 194: 194, 195: 195, 196: 196, 197: 197, 198: 198, 199: 199
+        };
+    }
+
+    get fun eq(): Bool {
+        let x: map<Int as uint16, Int as int32> = emptyMap();
+        x.set(1234, 2345);
+        x.set(8765, 7654);
+        let y = map<Int as uint16, Int as int32> {
+            8765: 7654,
+            1234: 2345,
+        };
+        return x == y;
+    }
+
+    get fun expression(): map<Int as uint16, Int as int32> {
+        return map<Int as uint16, Int as int32> {
+            1 + 2: 3 + 4,
+        };
+    }
+
+    get fun getSetResult(): Bool {
+        let x = map<Int as uint16, Int as int32> {
+            1: 1,
+            2: 2,
+        };
+        x.set(2, 4);
+        x.set(3, 3);
+        return x.get(1) == 1 && x.get(2) == 4 && x.get(3) == 3;
+    }
+
+    get fun decode(): Int {
+        return decode(1);
+    }
+
+    get fun anonymous(): Int {
+        return map<Int as uint16, Int as int32> {
+            1: 3,
+            2: 4,
+        }.get(1)!!;
+    }
+
+    get fun duplicates(): Int {
+        return map<Int as uint16, Int as int32> {
+            1: 3,
+            1: 4,
+        }.get(1)!!;
+    }
+}