Skip to content

feat!: Monomorphise everything during type checking #1441

Open
mark-koch wants to merge 27 commits intomainfrom
feat/early-mono
Open

feat!: Monomorphise everything during type checking #1441
mark-koch wants to merge 27 commits intomainfrom
feat/early-mono

Conversation

@mark-koch
Copy link
Collaborator

@mark-koch mark-koch commented Jan 14, 2026

This PR updates to compiler to monomorphize every generic definition. The resulting Hugr will contain no more generic functions.

Monomorphization happens during type checking, so every version is type checked separately.

This is a precursor for #1299.

Recommend reviewing commit by commit

BREAKING CHANGE: Removed MonomorphizableDef, MonomorphizedDef PartiallyMonomorphizedArgs, partially_monomorphize_args. Use GenericCheckableDef and MonoArgs instead.
BREAKING CHANGE: Context.generic_params renamed to Context.generic_inst, now storing an instantiation instead of parameters
BREAKING CHANGE: Removed NoneType.preserve and TupleType.preserve fields

@hugrbot
Copy link
Collaborator

hugrbot commented Jan 14, 2026

This PR contains breaking changes to the public Python API.

Breaking changes summary
guppylang-internals/src/guppylang_internals/nodes.py:0: GenericParamValue:
Public object was removed

guppylang-internals/src/guppylang_internals/engine.py:248: CompilationEngine.get_checked(mono_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/func_checker.py:135: check_global_func_def(ty):
Parameter was removed

guppylang-internals/src/guppylang_internals/checker/func_checker.py:135: check_global_func_def(globals):
Positional parameter was moved
Details: position: from 2 to 3 (+1)

guppylang-internals/src/guppylang_internals/checker/func_checker.py:135: check_global_func_def(generic_ty):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/func_checker.py:135: check_global_func_def(type_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:72: check_cfg(generic_params):
Parameter was removed

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:72: check_cfg(generic_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:208: check_bb(generic_params):
Parameter was removed

guppylang-internals/src/guppylang_internals/checker/cfg_checker.py:208: check_bb(generic_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/core.py:331: Globals.__init__(frame):
Parameter was removed

guppylang-internals/src/guppylang_internals/checker/core.py:331: Globals.__init__(def_id):
Parameter was added as required

guppylang-internals/src/guppylang_internals/checker/core.py:0: Context.generic_params:
Public object was removed

guppylang-internals/src/guppylang_internals/checker/errors/type_errors.py:0: UnpackableError.GenericSize:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/param.py:30: ParameterBase:
Base class was removed:
Old: [guppylang_internals.tys.common.ToHugr, abc.ABC]
New: [abc.ABC]

guppylang-internals/src/guppylang_internals/tys/param.py:0: ParameterBase.to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/param.py:0: TypeParam.to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/param.py:0: ConstParam.to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/ty.py:0: NoneType.preserve:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/ty.py:0: NoneType.__init__(preserve):
Parameter was removed

guppylang-internals/src/guppylang_internals/tys/ty.py:0: TupleType.preserve:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/ty.py:595: TupleType.__init__(preserve):
Parameter was removed

guppylang-internals/src/guppylang_internals/tys/common.py:0: ToHugrContext.type_var_to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/common.py:0: ToHugrContext.const_var_to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/common.py:0: QuantifiedToHugrContext:
Public object was removed

guppylang-internals/src/guppylang_internals/tys/parsing.py:0: TypeParsingCtx.__init__(allow_free_vars):
Positional parameter was moved
Details: position: from 3 to 4 (+1)

guppylang-internals/src/guppylang_internals/tys/parsing.py:0: TypeParsingCtx.__init__(self_ty):
Positional parameter was moved
Details: position: from 4 to 5 (+1)

guppylang-internals/src/guppylang_internals/compiler/expr_compiler.py:0: ExprCompiler.visit_GenericParamValue:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/expr_compiler.py:0: instantiation_needs_unpacking:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: PartiallyMonomorphizedArgs:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:72: MonoGlobalConstId:
Attribute value was changed:
Old: tuple[GlobalConstId, PartiallyMonomorphizedArgs | None]
New: tuple[GlobalConstId, MonoArgs]

guppylang-internals/src/guppylang_internals/compiler/core.py:0: EntryMonomorphizeError:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: CompilerContext.current_mono_args:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: CompilerContext.set_monomorphized_args:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:122: CompilerContext.compile(mono_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/compiler/core.py:168: CompilerContext.declare_global_func(mono_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: CompilerContext.type_var_to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: CompilerContext.const_var_to_hugr:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: partially_monomorphize_args:
Public object was removed

guppylang-internals/src/guppylang_internals/compiler/core.py:0: compile_variable_idx:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/value.py:63: CompiledCallableDef.compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/value.py:63: CompiledCallableDef.compile_call(dfg):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/value.py:63: CompiledCallableDef.compile_call(ctx):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/value.py:63: CompiledCallableDef.compile_call(node):
Positional parameter was moved
Details: position: from 5 to 4 (-1)

guppylang-internals/src/guppylang_internals/definition/value.py:0: CompiledCallableDef.load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/value.py:77: CompiledCallableDef.load(globals):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/value.py:77: CompiledCallableDef.load(ctx):
Parameter was added as required

guppylang-internals/src/guppylang_internals/definition/custom.py:0: CustomFunctionDef.compile_inner:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/custom.py:0: CustomFunctionDef.load:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/custom.py:215: CustomFunctionDef.has_var_args:
Attribute value was changed:
Old: field(default=False)
New: unset

guppylang-internals/src/guppylang_internals/definition/custom.py:0: CustomFunctionDef.load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/custom.py:0: CustomFunctionDef.compile_call:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/custom.py:0: CustomFunctionDef.__init__(has_var_args):
Parameter is now required

guppylang-internals/src/guppylang_internals/definition/function.py:122: ParsedFunctionDef.check(globals):
Positional parameter was moved
Details: position: from 1 to 2 (+1)

guppylang-internals/src/guppylang_internals/definition/function.py:122: ParsedFunctionDef.check(type_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/definition/function.py:0: CheckedFunctionDef.monomorphize:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/function.py:217: CompiledFunctionDef:
Base class was removed:
Old: [guppylang_internals.definition.function.CheckedFunctionDef, guppylang_internals.definition.value.CompiledCallableDef, guppylang_internals.definition.common.MonomorphizedDef, guppylang_internals.definition.value.CompiledHugrNodeDef]
New: [guppylang_internals.definition.function.CheckedFunctionDef, guppylang_internals.definition.value.CompiledCallableDef, guppylang_internals.definition.value.CompiledHugrNodeDef]

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.mono_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.monomorphize:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/function.py:244: CompiledFunctionDef.compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/function.py:244: CompiledFunctionDef.compile_call(dfg):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:244: CompiledFunctionDef.compile_call(ctx):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:244: CompiledFunctionDef.compile_call(node):
Positional parameter was moved
Details: position: from 5 to 4 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.__init__(mono_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.__init__(ty):
Positional parameter was moved
Details: position: from 5 to 4 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.__init__(docstring):
Positional parameter was moved
Details: position: from 6 to 5 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.__init__(cfg):
Positional parameter was moved
Details: position: from 7 to 6 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.__init__(func_def):
Positional parameter was moved
Details: position: from 8 to 7 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:0: load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/function.py:264: compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/function.py:264: compile_call(dfg):
Positional parameter was moved
Details: position: from 2 to 1 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:264: compile_call(ty):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/function.py:264: compile_call(func):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py:0: CompiledPytketDef.load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py:351: CompiledPytketDef.compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py:351: CompiledPytketDef.compile_call(dfg):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py:351: CompiledPytketDef.compile_call(ctx):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/pytket_circuits.py:351: CompiledPytketDef.compile_call(node):
Positional parameter was moved
Details: position: from 5 to 4 (-1)

guppylang-internals/src/guppylang_internals/definition/declaration.py:141: CheckedFunctionDecl:
Base class was removed:
Old: [guppylang_internals.definition.declaration.RawFunctionDecl, guppylang_internals.definition.common.CompilableDef, guppylang_internals.definition.value.CallableDef]
New: [guppylang_internals.definition.declaration.ParsedFunctionDecl, guppylang_internals.definition.common.CompilableDef]

guppylang-internals/src/guppylang_internals/definition/declaration.py:0: CheckedFunctionDecl.__init__(type_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/definition/declaration.py:0: CompiledFunctionDecl.load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/declaration.py:203: CompiledFunctionDecl.compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/declaration.py:203: CompiledFunctionDecl.compile_call(dfg):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/declaration.py:203: CompiledFunctionDecl.compile_call(ctx):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/declaration.py:203: CompiledFunctionDecl.compile_call(node):
Positional parameter was moved
Details: position: from 5 to 4 (-1)

guppylang-internals/src/guppylang_internals/definition/declaration.py:0: CompiledFunctionDecl.__init__(declaration):
Positional parameter was moved
Details: position: from 7 to 8 (+1)

guppylang-internals/src/guppylang_internals/definition/declaration.py:0: CompiledFunctionDecl.__init__(type_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/definition/common.py:21: ParsedDef:
Attribute value was changed:
Old: 'CheckableDef | CheckedDef'
New: 'CheckableDef | CheckableGenericDef | CheckedDef'

guppylang-internals/src/guppylang_internals/definition/common.py:22: CheckedDef:
Attribute value was changed:
Old: 'CompilableDef | MonomorphizableDef | CompiledDef'
New: 'CompilableDef | CompiledDef'

guppylang-internals/src/guppylang_internals/definition/common.py:0: MonomorphizableDef:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/common.py:0: MonomorphizedDef:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/overloaded.py:151: OverloadedFunctionDef.compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/overloaded.py:151: OverloadedFunctionDef.compile_call(dfg):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/overloaded.py:151: OverloadedFunctionDef.compile_call(ctx):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/overloaded.py:151: OverloadedFunctionDef.compile_call(node):
Positional parameter was moved
Details: position: from 5 to 4 (-1)

guppylang-internals/src/guppylang_internals/definition/overloaded.py:164: OverloadedFunctionDef.load_with_args(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/overloaded.py:164: OverloadedFunctionDef.load_with_args(dfg):
Positional parameter was moved
Details: position: from 2 to 1 (-1)

guppylang-internals/src/guppylang_internals/definition/overloaded.py:164: OverloadedFunctionDef.load_with_args(ctx):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/overloaded.py:164: OverloadedFunctionDef.load_with_args(node):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/traced.py:0: CompiledTracedFunctionDef.load_with_args:
Public object was removed

guppylang-internals/src/guppylang_internals/definition/traced.py:122: CompiledTracedFunctionDef.compile_call(type_args):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/traced.py:122: CompiledTracedFunctionDef.compile_call(dfg):
Positional parameter was moved
Details: position: from 3 to 2 (-1)

guppylang-internals/src/guppylang_internals/definition/traced.py:122: CompiledTracedFunctionDef.compile_call(ctx):
Positional parameter was moved
Details: position: from 4 to 3 (-1)

guppylang-internals/src/guppylang_internals/definition/traced.py:122: CompiledTracedFunctionDef.compile_call(node):
Positional parameter was moved
Details: position: from 5 to 4 (-1)


@github-actions
Copy link
Contributor

github-actions bot commented Jan 14, 2026

🐰 Bencher Report

Branchfeat/early-mono
TestbedLinux

🚨 2 Alerts

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_checkLatency
milliseconds (ms)
📈 plot
🚷 threshold
🚨 alert (🔔)
101.91 ms
(+29.80%)Baseline: 78.52 ms
82.44 ms
(123.62%)

tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compileLatency
milliseconds (ms)
📈 plot
🚷 threshold
🚨 alert (🔔)
203.26 ms
(+27.74%)Baseline: 159.12 ms
167.08 ms
(121.66%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
microseconds (µs)
(Result Δ%)
Upper Boundary
microseconds (µs)
(Limit %)
tests/benchmarks/test_big_array.py::test_big_array_check📈 view plot
🚷 view threshold
711,356.36 µs
(-8.37%)Baseline: 776,308.41 µs
815,123.83 µs
(87.27%)
tests/benchmarks/test_big_array.py::test_big_array_compile📈 view plot
🚷 view threshold
1,746,788.73 µs
(-3.28%)Baseline: 1,806,071.63 µs
1,896,375.21 µs
(92.11%)
tests/benchmarks/test_big_array.py::test_big_array_executable📈 view plot
🚷 view threshold
7,915,913.92 µs
(+1.30%)Baseline: 7,814,707.44 µs
8,205,442.81 µs
(96.47%)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_check📈 view plot
🚷 view threshold
🚨 view alert (🔔)
101,913.25 µs
(+29.80%)Baseline: 78,517.80 µs
82,443.69 µs
(123.62%)

tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile📈 view plot
🚷 view threshold
🚨 view alert (🔔)
203,255.99 µs
(+27.74%)Baseline: 159,119.31 µs
167,075.27 µs
(121.66%)

tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_executable📈 view plot
🚷 view threshold
879,467.67 µs
(-0.15%)Baseline: 880,745.92 µs
924,783.22 µs
(95.10%)
tests/benchmarks/test_prelude.py::test_import_guppy📈 view plot
🚷 view threshold
48.68 µs
(-1.78%)Baseline: 49.56 µs
52.04 µs
(93.54%)
🐰 View full continuous benchmarking report in Bencher

state = get_tracing_state()
# Only capture errors that are tagged with the span of the current
# tracing node
if err.error.span is None or to_span(state.node) == to_span(err.error.span):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly annoying:

Before this PR, we ensured that all functions called by comptime functions are checked before we enter capture_guppy_errors. Thus, errors in regular guppy functions would be reported using the normal mechanism.

Now, we can only check those called functions while we're tracing (since we only know the monomorphic instantiation at that point) so their errors would be intercepted here. This guard is a hacky solution to avoid this.

The proper solution would be to do comptime tracing during checking, instead of during Hugr construction, but that's a bigger refactor

Copy link
Contributor

@acl-cqc acl-cqc Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First thing to note is that the method doc is wrong - it mentions "GuppyComptimeException" and such does not exist!

IIUC the issue here is to distinguish between errors checking the guppy generated directly by the comptime block, and those in newly-created instantations of non-comptime methods?

Are you sure that the refactor to do comptime tracing during checking, isn't something we should do first? I think it might help with a few other questions/concerns here and I suspect it'll be a requirement for signature metaprogramming (where, in some sense, many more functions are processed in a way that's like tracing - perhaps all, perhaps not).

Failing that, is there an intermediate solution - can you make guppy-comptime enqueue the instantiations that it needs, and then do "another round" of checking (emptying the queue) after comptime?

@codecov-commenter
Copy link

codecov-commenter commented Jan 14, 2026

Codecov Report

❌ Patch coverage is 96.10390% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.34%. Comparing base (c16b96b) to head (fc98357).

Files with missing lines Patch % Lines
...ls/src/guppylang_internals/checker/expr_checker.py 87.50% 2 Missing ⚠️
...ls/src/guppylang_internals/checker/stmt_checker.py 0.00% 2 Missing ⚠️
.../src/guppylang_internals/compiler/expr_compiler.py 93.33% 1 Missing ⚠️
...pylang-internals/src/guppylang_internals/engine.py 98.03% 1 Missing ⚠️
...ylang_internals/std/_internal/compiler/platform.py 0.00% 1 Missing ⚠️
...-internals/src/guppylang_internals/tracing/util.py 91.66% 1 Missing ⚠️
...ylang-internals/src/guppylang_internals/tys/arg.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1441      +/-   ##
==========================================
- Coverage   93.54%   93.34%   -0.20%     
==========================================
  Files         127      127              
  Lines       11796    11703      -93     
==========================================
- Hits        11034    10924     -110     
- Misses        762      779      +17     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment on lines 3 to +6
10 | @guppy
11 | def main(n: nat @ comptime) -> None:
11 | def bar(n: nat @ comptime) -> None:
12 | foo[n](42)
| ^^ Expected constant `n`, got `42`
| ^^ Expected constant `43`, got `42`
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These errors are worse now, classic problem with C++ style template debugging :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comments!

Comment on lines -10 to -14
Note: Parameter `tag` was instantiated to `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaa`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shame that we also no longer have this

Maybe, we want this as a more general hint to tell us what parameters were instantiated to?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, can we not ("just"!!) include the current monomorphization as part of the context of the error? (I guess it would be potentially a chain of instantiations, which might be hard to identify given the worklist, but even just the nearest-enclosing instantiation would be something)

@mark-koch mark-koch requested a review from acl-cqc January 14, 2026 17:32
@mark-koch mark-koch marked this pull request as ready for review January 14, 2026 17:32
@mark-koch mark-koch requested a review from a team as a code owner January 14, 2026 17:32
Copy link
Contributor

@acl-cqc acl-cqc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Mark! A lot to think about here... a few more bits you can cleanup/remove, some thoughts on typechecking, some possible further refactors.

I see that uninstantiated functions do not produce errors (even if they contain e.g. undeclared variables), simply because uncalled functions do not...that does feel a bit sneaky/surprising (I mean it's kinda like python "errors delayed until runtime" but we would often use mypy to detect them ahead of time and guppy takes the role of mypy...)

OTOH it's good to see that a function that's invalid for all type arguments, produces the error only for one set of type arguments....although I think that is just because we only issue one error before we stop....

However this does not produce an error:

T = guppy.type_var("T")

@guppy
def baz(x: T, y: T) -> int:
    return x + y

@guppy
def main(x: int, y: int) -> None:
    baz(x, y)

main.compile_function()

see comment on engine.py but I'd be in favour of doing that even for unreached functions, really!

inputs: Row[Variable],
return_ty: Type,
generic_params: dict[str, Parameter],
generic_inst: dict[str, Argument],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super-nit but I preferred generic_args - clearer that it's just the arguments, whereas an "instantiation" might be just that, or (say) both the arguments and the (parametric/polymorphic) thing being instantiated

inputs: Row[Variable],
return_ty: Type,
generic_params: dict[str, Parameter],
generic_args: dict[str, Argument],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth documenting/commenting that this function also creates a new instantiation

f_locals: dict[str, Any]
f_globals: dict[str, Any]
f_builtins: dict[str, Any]
def_id: DefId | None
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could clarify the doc comment: "Wrapper around one Definition in the DEF_STORE" or something like that. (Can it really be "in a stack frame"? In a function scope, perhaps - I would have thought that stack frames were dynamic, runtime, objects?)

@@ -463,7 +466,7 @@ def _check_global(
case ParamDef():
# Check if this parameter is in our generic_params
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would a ParamDef have gotten into globals if it isn't in scope?

# Check if this parameter is in our generic_params
# (e.g., used in type signature)
if name in self.ctx.generic_params:
if name in self.ctx.generic_param_inst:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'll just note that there are some checks that are independent of instantiation, that could be performed once on the parsed/parametric non-instantiated function. This is one such. So....if I declare a polymorphic function, but don't instantiate it, and it contains type errors (irrespective of the "values"==types of those type parameters)...presumably we will now have no error?

def check_valid_entry_point(defn: ParsedDef) -> MonoArgs:
"""Checks if the given definition is a valid compilation entry-point.

In particular, ensures that the definition doesn't depend on generic parameters and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super-nit: the "and returns...." should be in the first sentence....or, and perhaps better, the () could just go in the caller

state = get_tracing_state()
# Only capture errors that are tagged with the span of the current
# tracing node
if err.error.span is None or to_span(state.node) == to_span(err.error.span):
Copy link
Contributor

@acl-cqc acl-cqc Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First thing to note is that the method doc is wrong - it mentions "GuppyComptimeException" and such does not exist!

IIUC the issue here is to distinguish between errors checking the guppy generated directly by the comptime block, and those in newly-created instantations of non-comptime methods?

Are you sure that the refactor to do comptime tracing during checking, isn't something we should do first? I think it might help with a few other questions/concerns here and I suspect it'll be a requirement for signature metaprogramming (where, in some sense, many more functions are processed in a way that's like tracing - perhaps all, perhaps not).

Failing that, is there an intermediate solution - can you make guppy-comptime enqueue the instantiations that it needs, and then do "another round" of checking (emptying the queue) after comptime?

param_var_mapping: dict[str, Parameter] = field(default_factory=dict)

#: Type parameters that are bound to concrete arguments
param_inst: Mapping[str, Argument] = field(default_factory=dict)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I was saying you should call generic_args earlier - but param_inst could be good too - and/or maybe a new type: it's just like Inst except keyed by string rather than int

Comment on lines 3 to +6
10 | @guppy
11 | def main(n: nat @ comptime) -> None:
11 | def bar(n: nat @ comptime) -> None:
12 | foo[n](42)
| ^^ Expected constant `n`, got `42`
| ^^ Expected constant `43`, got `42`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my other comments!


@guppy
def main(n: nat @ comptime) -> None:
def bar(n: nat @ comptime) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's not even obvious to me that foo should take two arguments (one static/"type argument", one "runtime"/value argument)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Nested function definitions don't have a frame in the DEF_STORE

4 participants