Skip to content

[WIP] Struct methods#623

Draft
iglosiggio wants to merge 18 commits intomainfrom
experiment/struct-methods
Draft

[WIP] Struct methods#623
iglosiggio wants to merge 18 commits intomainfrom
experiment/struct-methods

Conversation

@iglosiggio
Copy link
Copy Markdown
Member

This is just a proof-of-concept! The implementation side is really hacky

Proposed Changes

  • Remove special-casing of self as a function argument
  • Add an ExpressionBuilder for bound functions (allowing us to bind self when accessing a method field)
  • Process methods in struct declarations.

UGLY: current implementation modifies the methods dictionary for the struct type. I didn't find any other way to circumvent the issue where you need the struct type to create the function type but you need the type fully defined and registered in order to find it in the list of globals.

The definitive implementation would probably support not only arc4.Struct but also puyapy.Struct and named tuples.

This is an interesting feature for named tuples: having a single-byte-slot in a named tuple allows for nonstandard serialization layouts. This idea was born from looking at @cusma's implementation of ARC89.

@engineering-ci
Copy link
Copy Markdown

engineering-ci bot commented Dec 29, 2025

Name Status O0 bytes O1 bytes O2 bytes O0 ops O1 ops O2 ops
structure_methods 🆕 +646 +156 +153 +420 +97 +96

@engineering-ci
Copy link
Copy Markdown

engineering-ci bot commented Dec 29, 2025

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/puya
   __main__.py36683%45, 52–53, 71–72, 78
   arc32.py89496%74, 125–130
   arc56.py81495%285–287, 297
   artifact_sorter.py57296%32, 89
   compile.py210797%98–99, 173–174, 182, 343, 401
   context.py52198%39
   errors.py41198%59
   log.py2656675%82, 92–93, 98–105, 120–128, 150, 186–187, 229–231, 234–236, 238, 251–267, 296, 302, 312–317, 320, 388–389, 426–428, 441–455, 479
   main.py41783%31, 50–55
   parse.py83495%31, 40, 50, 56
   program_refs.py28293%45, 56
   utils.py2261494%37, 196, 201–204, 222–224, 258, 265, 288, 290, 309, 348
src/puya/awst
   function_traverser.py330399%66, 76, 413
   nodes.py13116395%113, 117–120, 160, 164–167, 462, 527, 700, 769, 795, 853, 873, 896, 935, 980, 1009–1010, 1032, 1036, 1105, 1126, 1157–1158, 1185, 1188–1190, 1208, 1242, 1245, 1283, 1288, 1415, 1441, 1474, 1506, 1532, 1537, 1784, 1928, 1947, 2013, 2051, 2060, 2279, 2328, 2346, 2350, 2468, 2615, 2620, 2625, 2633, 2638, 2643, 2680–2681, 2691
   serialize.py761284%21–25, 49, 94–100, 117, 121
   to_code_visitor.py4791198%149, 260, 297, 343, 380, 494–497, 677, 705, 709
   txn_fields.py101199%53
   wtypes.py376998%233, 314, 366, 379, 390, 395–396, 437, 466
src/puya/awst/validation
   base_invoker.py47491%55, 62, 72–76
   immutable.py33294%29, 37
   inner_transactions.py196199%168
   labels.py30873%25–27, 32, 36–41
src/puya/ir
   _arc4_default_args.py1101586%63–64, 79, 92, 94, 96, 98, 100, 102, 108, 115, 136–137, 232, 239
   contract_metadata.py229797%56, 206–210, 308–314
   arc4_router.py259598%169, 233, 335, 353, 519
   arc4_types.py1621988%26–27, 46, 50, 54, 58, 62, 70, 96, 103, 110, 117, 129, 135, 139, 151, 211, 215, 230
   avm_ops.py324199%46
   avm_ops_models.py37295%15, 23
   context.py113695%79, 91, 98, 101, 103, 112
   encodings.py204697%230, 233, 239, 257, 294, 298
   main.py2241494%117, 126–132, 142–148, 174, 342–343, 355, 363
   models.py7942797%211, 218, 413, 435, 496, 514, 518, 634–635, 759–760, 765, 771–775, 788, 845, 875, 931, 992, 1037, 1122, 1166, 1181, 1193, 1220, 1288–1289
   mutating_register_context.py87397%48–49, 51
   op_utils.py163299%212–219
   ssa.py151299%52–53
   types.py3133489%56, 59, 62, 75, 80–82, 86–88, 128, 142, 181, 248, 251, 262, 271, 275, 278, 296, 302, 400–404, 506–507, 512, 529–530, 555, 561, 564, 567, 570
   visitor.py185597%184, 285, 306, 312, 358
   visitor_mutator.py223797%89–92, 94, 193, 234
src/puya/ir/builder
   _utils.py39295%50, 52
   assignment.py119794%63, 79, 190, 192–193, 220, 265
   blocks.py134795%55, 92–96, 158, 166, 231
   bytes.py701480%17–49, 138
   callsub.py1091091%35–36, 57–60, 73, 118–122
   dynamic_array.py167597%81–82, 135–136, 172
   encoding_validation.py71199%121
   flow_control.py104298%53, 57
   iteration.py239598%103–104, 120, 152, 230
   itxn.py5805191%154–155, 157, 171, 209–210, 234–235, 347–350, 643, 662–680, 711, 800, 824, 839, 846, 850, 886, 898, 902, 916, 928, 934, 940, 944, 964, 1028, 1038, 1042, 1046, 1062, 1074, 1102, 1108, 1112, 1116, 1120, 1138, 1149, 1160
   main.py7474993%167, 292, 304–323, 333–335, 402, 426, 450, 474–475, 499, 525, 567, 572–573, 584–585, 753, 778–779, 811, 846, 927, 992, 1091–1095, 1178, 1246, 1249, 1402, 1428, 1456–1457, 1608, 1628, 1683–1685
   sequence.py136696%191–196, 217–220, 235–236
   storage.py208598%117, 335–339, 351
src/puya/ir/builder/aggregates
   arc4_codecs.py3172592%58, 94, 147–148, 203, 271, 322, 335, 351, 394, 412, 428–430, 444–446, 520, 537, 539, 556–557, 572–573, 602
   main.py88397%76, 102, 116
   sequence.py156199%144
src/puya/ir/destructure
   coalesce_locals.py1442185%198, 209, 213–224, 230–231, 246–251
   critical_edges.py32197%24
   parcopy.py72199%80
src/puya/ir/optimize
   _call_graph.py32197%42
   add_box_extract_replace.py279598%235–236, 240, 276, 516
   assignments.py114496%48, 168, 184–185
   compiled_reference.py101595%56, 87, 168–173
   constant_propagation.py71199%90
   control_op_simplification.py77199%170
   inlining.py2441195%34–43, 47, 54, 417, 421, 425, 435, 439
   inner_txn.py36294%49–50
   intrinsic_simplification.py8094295%268, 514, 545, 556–557, 625–626, 647, 752, 833, 864–893, 1244–1246, 1325, 1351, 1353, 1426, 1440, 1471, 1477, 1479, 1481, 1486, 1488, 1490, 1494, 1568
   repeated_loads_elimination.py155299%156, 173
src/puya/ir/validation
   _base.py30197%28
   compile_reference_validator.py20290%24, 30
   min_avm_version_validator.py15473%16–20
   op_run_mode_validator.py19195%29
   slot_reservation.py18194%20
src/puya/mir
   aligned_writer.py63297%21, 61
   builder.py2072389%129–131, 159–161, 180, 183, 186, 189, 192, 195, 198, 254, 261, 401, 404, 407, 410, 413, 416, 419, 422
   main.py77199%37
   models.py499499%120, 255, 321, 727
   output.py61198%20
   visitor.py86199%175
src/puya/mir/stack_allocation
   f_stack.py95397%58–67
   l_stack.py117199%71
   peephole.py41393%38, 40, 43
   x_stack.py203399%33, 331–335
src/puya/teal
   builder.py200299%74, 114
   models.py470199%461
   stack_manipulations.py31197%42
src/puya/teal/optimize
   constant_block.py93496%43, 126, 163, 180
   constant_stack_shuffling.py94990%54–55, 77–78, 94–100
   main.py176498%141, 200, 227–228
   peephole.py137299%170, 295
   repeated_rotations.py51590%16, 52–55
   repeated_rotations_search.py90693%35, 41–42, 58, 68–69
src/puya/ussemble
   assemble.py201399%253, 273, 317
   models.py26196%16
   op_spec_models.py22195%20
src/puyapy
   __main__.py43686%155, 160–165, 204, 213
   client_gen.py1101190%59–60, 78–82, 86, 200, 207–208, 228
   compile.py911386%41, 57, 68, 72, 132–133, 141–144, 153–156
   find_sources.py97496%25, 46, 105, 124
   interpreter_data.py18289%32–33
   models.py103199%76
   parse.py2703587%115–120, 179, 189, 197, 209–210, 226–228, 239–244, 252–257, 261, 436, 440–441, 473–476, 480–481, 494–496, 501
   template.py32875%10–11, 18–19, 27–28, 34, 37
src/puyapy/awst_build
   arc4_client.py1012674%45–49, 57, 69, 75, 79, 96, 108, 111–112, 118, 121, 124, 130, 133, 136–141, 144, 147, 150, 153, 156, 159, 162, 165
   arc4_client_gen.py134894%30, 89–90, 100, 102, 117–118, 188
   arc4_decorators.py2485179%45, 90–91, 98–100, 111–115, 123–125, 146–148, 151, 179, 193, 207, 209, 242, 255–256, 270–271, 285–286, 293, 296–302, 307, 314, 335, 343, 347, 357–361, 377, 379, 382–383, 392, 395–396, 398
   arc4_utils.py115497%162, 169, 183, 211
   base_mypy_visitor.py1552882%59, 69, 92–100, 167–173, 191, 199–212, 226, 228, 230, 281, 285, 289, 291, 298–318
   context.py2696177%57, 60, 70–71, 89–90, 130, 176, 179–180, 182, 231, 236, 242–246, 253, 262, 264, 267–269, 271, 278, 280, 290–303, 315, 329, 332–344, 362, 380, 405, 414–420
   contract.py3293191%124, 176, 205–209, 249, 251, 255, 259, 267, 279, 281, 352, 355, 367, 375, 378, 381, 384, 387, 390, 393, 396, 399, 493–497, 546–550, 624–628, 686, 712
   intrinsic_factory.py26292%67–69
   intrinsic_models.py49198%55
   main.py42198%41
   module.py4726486%140, 165, 171–188, 205–206, 213, 222–223, 230–234, 256, 285, 305–306, 317, 339–342, 352–354, 360, 377–380, 393, 427, 456–457, 478–479, 535–536, 562, 573, 576, 582, 595, 601, 607, 627, 647, 652, 656, 665–669, 734, 744, 752, 834, 842, 844
   pytypes.py6406390%87, 97–99, 104, 111, 116–118, 122–124, 151–152, 192, 210–216, 239, 263, 305, 343–345, 376, 443, 452, 471, 491, 495, 610–611, 695–697, 711–712, 779–780, 884, 895–896, 934–935, 982–983, 988, 1057–1058, 1081–1082, 1233–1234, 1262, 1290, 1326–1328, 1368, 1378–1379
   subroutine.py6524294%167, 187–188, 285, 311–312, 330, 347, 372, 380, 391, 424–427, 540–541, 597, 759, 770, 772, 781, 799, 802, 840, 846, 907, 915–918, 924, 975, 980, 983–989, 1080, 1100, 1256, 1296, 1322, 1338–1339, 1348
   utils.py2032886%30, 47–51, 79–80, 102, 141–142, 144, 188–189, 240, 248, 253, 266–270, 275–278, 285, 329–332
src/puyapy/awst_build/eb
   _base.py1271687%52, 57–59, 64, 71, 80–82, 143, 154, 184, 189, 199, 210, 224–226
   _bytes_backed.py48296%30–31
   _expect.py1221786%25, 36, 85–88, 107, 160–161, 219–222, 232–235
   _literals.py1563478%44, 65, 77, 96, 125, 142, 156–160, 164, 168–174, 184–198, 203
   _type_registry.py41393%271–272, 285
   utils.py62395%36–38, 108
   biguint.py101694%56, 100, 139, 155–156, 158
   binary_bool_op.py105397%153, 161, 171
   bool.py55984%38–42, 58, 69, 82, 96
   bytes.py1731691%102–103, 130–131, 136–137, 143–144, 147, 155, 233, 271, 292–293, 313–314
   compiled.py70987%85–89, 126–130, 153
   conditional_literal.py1333474%97, 101, 161, 165–168, 177–179, 202–205, 214, 218, 222–225, 240–252, 261–262, 273–276
   contracts.py76889%55, 61, 63, 66, 92, 105, 107, 112
   dict.py27581%24, 32–34, 38
   ensure_budget.py31197%46
   fixed_bytes.py2741196%110–111, 142, 250, 335, 412, 464, 516, 522, 586–587
   interface.py91298%317–319
   intrinsics.py120893%55–56, 70, 91, 98, 111, 118, 210
   log.py43491%45–46, 51, 60
   logicsig.py15193%25
   none.py27485%17, 27–28, 37
   size_of.py25388%31–33
   string.py1441391%71, 115–116, 135, 140, 183, 190, 194, 206, 280–282, 302
   subroutine.py891682%46, 50–53, 68, 71–78, 93, 101–102, 104–107, 112
   template_variables.py37295%29, 57
   tuple.py3471297%92, 99, 140, 168, 271, 379–380, 499, 575, 586–587, 650
   uint64.py108595%57, 118–119, 167–168
   uint64_enums.py40295%40, 45
   unsigned_builtins.py1552286%73, 80, 104, 128, 132, 136, 140, 148, 152, 156, 160, 164, 174, 178, 184, 195, 201, 207, 246, 278, 290, 302
src/puyapy/awst_build/eb/arc4
   _base.py105595%183–186, 197, 227–228
   _utils.py123596%44, 103, 134, 197, 201
   abi_call.py3492194%123, 129, 150, 220, 233–234, 316, 327, 407, 431, 464, 483–484, 503, 538, 582, 618, 685, 770–771, 788
   address.py73297%56, 115
   bool.py60198%42
   dynamic_array.py122993%109–110, 128, 152, 206, 221–222, 225–228
   dynamic_bytes.py68396%98–100
   emit.py53296%40–43
   string.py100793%54–55, 105, 126, 131–134
   struct.py91199%58
   tuple.py921287%51–53, 91–94, 97–98, 137–140, 145, 170
   ufixed.py74297%51, 110
   uint.py96298%101–106
src/puyapy/awst_build/eb/native
   struct.py90199%84
src/puyapy/awst_build/eb/reference_types
   account.py82199%179
   application.py45198%39
   asset.py65198%47
src/puyapy/awst_build/eb/storage
   _storage.py1082081%58, 66, 70, 74, 78, 82, 86, 90, 94, 104, 108, 112, 116, 122, 133, 139, 145, 157–159
   _value_proxy.py55395%42, 50, 54
   box.py113199%174
   box_map.py164299%199, 265
   box_ref.py1111785%76–80, 89–93, 103, 111, 127, 135, 148, 153–162
   global_state.py127695%103–104, 113–114, 163–164
   local_state.py1371192%98–99, 103, 150, 154, 158, 168, 172, 196, 253, 277
src/puyapy/awst_build/eb/transaction
   base.py39295%22, 42
   group.py51198%47
   inner.py54296%96–97
   inner_params.py88594%65, 77, 81, 169, 171
   itxn_args.py60198%72
src/puyapy/lsp
   __main__.py17170%1–44
   analyse.py26819129%58–70, 76–183, 186–197, 201–216, 226–246, 249, 252–254, 265–281, 288–332, 349–373, 381, 385–386, 395–399, 404–411, 434–449, 461, 465, 467, 473, 482–487, 491
   log.py44440%1–89
   server.py1338238%62–70, 74–79, 86–136, 153–155, 158–162, 165–172, 179–197, 200, 203–208, 218–220
src/puyapy/validation
   arc4_copy.py130298%35, 39
TOTAL30261201193% 

Tests Skipped Failures Errors Time
1231 3 💤 0 ❌ 0 🔥 8m 10s ⏱️

@iglosiggio iglosiggio force-pushed the experiment/struct-methods branch from 81ca48d to b5ca8f2 Compare December 30, 2025 20:47
@iglosiggio iglosiggio force-pushed the experiment/struct-methods branch from 0985fa0 to e72d131 Compare January 2, 2026 13:04
@iglosiggio iglosiggio changed the title [WIP] Hacky implementation of struct methods [WIP] Struct methods Jan 5, 2026
@iglosiggio iglosiggio added do-not-merge: needs ADR This change has to be documented as part of the compiler architecture or the language description. and removed do not merge labels Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge: needs ADR This change has to be documented as part of the compiler architecture or the language description.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant