Skip to content

Commit d09559d

Browse files
sudo-owenclaude
andauthored
Claude/refactor sol2ts transpiler yp1ez (#55)
* Refactor sol2ts transpiler into modular architecture Split the monolithic 6,065-line sol2ts.py into a modular structure: - transpiler/lexer/ - Tokenization (tokens.py, lexer.py) - TokenType enum, Token dataclass - Keyword/operator mappings - Lexer class - transpiler/parser/ - AST and parsing (ast_nodes.py, parser.py) - All AST node dataclasses (SourceUnit, ContractDefinition, etc.) - Recursive descent parser - transpiler/types/ - Type system (registry.py, mappings.py) - TypeRegistry for cross-file type discovery - Solidity-to-TypeScript type mappings - Default value helpers - transpiler/codegen/ - Code generation helpers (yul.py, abi.py, context.py) - YulTranspiler for inline assembly - AbiTypeInferer for ABI encoding - CodeGenerationContext for state management The original sol2ts.py is preserved for backward compatibility. New code can import from the modules directly: from transpiler.lexer import Lexer from transpiler.parser import Parser from transpiler.types import TypeRegistry https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Complete modular refactoring of sol2ts transpiler Decompose the monolithic TypeScriptCodeGenerator into focused generator classes: - BaseGenerator: Shared utilities for all generators - TypeConverter: Solidity to TypeScript type conversions - ExpressionGenerator: Expression AST code generation - StatementGenerator: Statement AST code generation - FunctionGenerator: Function/constructor generation - DefinitionGenerator: Struct/enum/constant generation - ImportGenerator: Import statement generation - ContractGenerator: Contract class generation - generator.py: Main orchestrator coordinating all generators Benefits: - Single responsibility for each generator class - Easier testing and maintenance of individual components - Cleaner separation of concerns - Total 116 Solidity files transpiled successfully in testing https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Fix ABI type inference for method return types, finalize modular refactoring Changes: - Add current_method_return_types tracking in CodeGenerationContext - Populate method return types in ContractGenerator._setup_contract_context - Fix ExpressionGenerator._infer_single_abi_type to lookup method return types - Update test_transpiler.py to use modular imports - Replace old 6,065-line sol2ts.py with new 264-line modular version - Update transpiler/__init__.py exports for new module structure All 8 unit tests pass and 111 Solidity files transpile successfully. https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Add ts-output/ to gitignore Generated TypeScript output should not be committed. https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Add metadata generation for factories.ts dependency injection - Create MetadataExtractor to collect contract info from ASTs - Create FactoryGenerator to produce factories.ts with container registrations - Integrate metadata generation with --emit-metadata flag - Auto-generate interface aliases (IEngine -> Engine, etc.) - Auto-generate lazy singletons with constructor dependencies All 38 TypeScript tests now pass (previously 31 passed, 1 suite failed). https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Enable previously skipped tests for SignedMatchmaker and GachaTeamRegistry Both modules now import successfully with the runtime replacements for EIP712 and Ownable. All 40 tests pass. https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Replace CHANGELOG.md with README.md documenting modular architecture - Document new module structure (lexer/, parser/, types/, codegen/) - Document code generator architecture with specialized generators - Update test counts (40 TypeScript tests, 8 Python tests) - Document factories.ts generation with --emit-metadata - Preserve clear, no-frills documentation style https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Refactor parser.py to reduce repetition with DRY helpers Add utility methods: - _parse_binary_op(): Generic left-associative binary operator parsing - parse_comma_separated(): Parse comma-separated lists with end token - parse_storage_location(): Parse storage/memory/calldata - skip_balanced(): Skip balanced bracket pairs (parens, braces) Apply to simplify: - 10 binary operator methods now use _parse_binary_op helper - Event/error/function parameters use parse_comma_separated - State variable/function attributes use token->value dictionaries - Function skip, try/catch, base contracts use skip_balanced All 40 TypeScript tests and Python unit tests pass. https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Apply DRY refactoring to codegen modules and fix types/ naming conflict - expression.py: Extract _resolve_abi_base_type() and _infer_expression_type() to share logic between ABI type inference methods (~80 lines reduced) - statement.py: Extract _generate_body_statements() for loop body generation - function.py: Extract _get_visibility_modifier() and _get_static_modifier() - Rename types/ to type_system/ to avoid shadowing Python's standard library All 40 TypeScript tests and 8 Python unit tests pass. https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Fix bigint index type error and single-file import path calculation - contract.py: Convert non-string mapping keys to String() for Record indexing Fixes TS2538: Type 'bigint' cannot be used as an index type - sol2ts.py: Use discovery_dir as source_dir in single-file mode Fixes import paths being '../' instead of './' when processing single files https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Fix bytes32 cast and Constants reference in Yul assembly - type_converter.py: Handle bytes32/bytesN casts of non-literal expressions Convert bigint to padded hex string at runtime: `0x${expr.toString(16).padStart(64, "0")}` Fixes TS2322: Type 'bigint' is not assignable to type 'string' - yul.py: Prefix ALL_CAPS identifiers with Constants. Fixes TS2304: Cannot find name 'PACKED_CLEARED_MON_STATE' https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw * Use type registry for Yul constant resolution instead of heuristic - YulTranspiler now accepts known_constants set from type registry - StatementGenerator passes ctx.known_constants to YulTranspiler - Replaces fragile ALL_CAPS heuristic with proper type tracking https://claude.ai/code/session_01GjrjyxhRAhckrhyJGogaQw --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent b09b79d commit d09559d

5 files changed

Lines changed: 33 additions & 6 deletions

File tree

transpiler/codegen/contract.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -402,12 +402,15 @@ def _generate_mapping_mutator(
402402
key_name = f'key{key_index}'
403403
key_params.append(f'{key_name}: {key_ts_type}')
404404

405+
# Convert non-string keys to string for Record indexing
406+
key_access = key_name if key_ts_type == 'string' else f'String({key_name})'
407+
405408
if current_type.value_type.is_mapping:
406409
null_coalesce_lines.append(
407-
f'{body_indent}{access_path}[{key_name}] ??= {{}};'
410+
f'{body_indent}{access_path}[{key_access}] ??= {{}};'
408411
)
409412

410-
access_path = f'{access_path}[{key_name}]'
413+
access_path = f'{access_path}[{key_access}]'
411414
current_type = current_type.value_type
412415
key_index += 1
413416

transpiler/codegen/statement.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def __init__(
7272
super().__init__(ctx)
7373
self._expr = expr_generator
7474
self._type_converter = type_converter
75-
self._yul_transpiler = YulTranspiler()
75+
self._yul_transpiler = YulTranspiler(known_constants=ctx.known_constants)
7676

7777
# =========================================================================
7878
# MAIN DISPATCH

transpiler/codegen/type_converter.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,15 +190,22 @@ def generate_type_cast(
190190
if expr != 'this' and not expr.startswith('"') and not expr.startswith("'"):
191191
return f'{expr}._contractAddress'
192192

193-
# Handle bytes32 literals
193+
# Handle bytes32 literals and expressions
194194
if type_name == 'bytes32':
195195
if isinstance(inner_expr, Literal) and inner_expr.kind in ('number', 'hex'):
196196
return self._to_padded_bytes32(inner_expr.value)
197+
# Non-literal: convert bigint to padded hex string at runtime
198+
expr = generate_expression_fn(inner_expr)
199+
return f'`0x${{{expr}.toString(16).padStart(64, "0")}}`'
197200

198201
# Handle bytes types
199202
if type_name.startswith('bytes') and type_name != 'bytes':
200203
if isinstance(inner_expr, Literal) and inner_expr.kind in ('number', 'hex'):
201204
return self._to_padded_bytes32(inner_expr.value)
205+
# Non-literal: convert bigint to padded hex string at runtime
206+
byte_size = int(type_name[5:]) if type_name[5:].isdigit() else 32
207+
expr = generate_expression_fn(inner_expr)
208+
return f'`0x${{{expr}.toString(16).padStart({byte_size * 2}, "0")}}`'
202209

203210
# For numeric types (uint256, int128, etc.), just generate the inner expression
204211
# TypeScript's bigint handles the underlying value

transpiler/codegen/yul.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ class YulTranspiler:
4848
- mstore/mload → memory operations (usually no-op for simulation)
4949
"""
5050

51+
def __init__(self, known_constants: set = None):
52+
"""Initialize with optional set of known constant names.
53+
54+
Args:
55+
known_constants: Set of constant names that should be prefixed with 'Constants.'
56+
"""
57+
self._known_constants = known_constants or set()
58+
5159
def transpile(self, yul_code: str) -> str:
5260
"""
5361
Transpile a Yul assembly block to TypeScript.
@@ -285,6 +293,10 @@ def _transpile_expr(self, expr: str, slot_vars: Dict[str, str]) -> str:
285293
if expr.isdigit():
286294
return f'{expr}n'
287295

296+
# Check if identifier is a known constant from type registry
297+
if expr in self._known_constants:
298+
return f'Constants.{expr}'
299+
288300
# Return as-is (identifier)
289301
return expr
290302

transpiler/sol2ts.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
- Interface and contract inheritance
1414
1515
Usage:
16-
python -m transpiler src/
16+
python transpiler/sol2ts.py src/
1717
1818
This refactored version uses a modular architecture with separate packages for:
1919
- lexer: Tokenization (tokens.py, lexer.py)
@@ -244,6 +244,8 @@ def main():
244244
parser.add_argument('input', help='Input Solidity file or directory')
245245
parser.add_argument('-o', '--output', default='transpiler/ts-output', help='Output directory')
246246
parser.add_argument('--stdout', action='store_true', help='Print to stdout instead of file')
247+
parser.add_argument('-d', '--discover', action='append', metavar='DIR',
248+
help='Directory to scan for type discovery')
247249
parser.add_argument('--stub', action='append', metavar='CONTRACT',
248250
help='Contract name to generate as minimal stub')
249251
parser.add_argument('--emit-metadata', action='store_true',
@@ -254,12 +256,15 @@ def main():
254256
args = parser.parse_args()
255257

256258
input_path = Path(args.input)
257-
discovery_dirs = [str(input_path)] if input_path.is_dir() else [str(input_path.parent)]
259+
discovery_dirs = args.discover or ([str(input_path)] if input_path.is_dir() else [str(input_path.parent)])
258260
stubbed_contracts = args.stub or []
259261
emit_metadata = args.emit_metadata or args.metadata_only
260262

261263
if input_path.is_file():
264+
# Use first discovery dir as source_dir for correct import path calculation
265+
source_dir = discovery_dirs[0] if discovery_dirs else str(input_path.parent)
262266
transpiler = SolidityToTypeScriptTranspiler(
267+
source_dir=source_dir,
263268
output_dir=args.output,
264269
discovery_dirs=discovery_dirs,
265270
stubbed_contracts=stubbed_contracts,

0 commit comments

Comments
 (0)