Skip to content

feat!: Monomorphise everything during type checking #1441

Merged
mark-koch merged 66 commits intomainfrom
feat/early-mono
Apr 2, 2026
Merged

feat!: Monomorphise everything during type checking #1441
mark-koch merged 66 commits intomainfrom
feat/early-mono

Conversation

@mark-koch
Copy link
Copy Markdown
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.

Also closes #1032 as a drive-by.

In particular, note the breaking internal change that guppylang_internals.tys.subst.Inst is now a tuple instead of a Sequence.

BREAKING CHANGE: Removed MonomorphizableDef and MonomorphizedDef. Use GenericCheckableDef instead.
BREAKING CHANGE: CompiledCallableDef.compile_call no longer takes type args
BREAKING CHANGE: Removed CompiledCallableDef.load_with_args. Use CompiledCallableDef.load instead.
BREAKING CHANGE: Removed ParameterBase.to_hugr since we no longer need to be able to turn params into Hugr params
BREAKING CHANGE: Removed PartiallyMonomorphizedArgs, partially_monomorphize_args, require_monomorphization, compile_variable_idx MonoArgs, CompilerContext.{current_mono_args, set_monomorphized_args, type_var_to_hugr, const_var_to_hugr}, instantiation_needs_unpacking.
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
BREAKING CHANGE: Inst is now a tuple instead of a Sequence
BREAKING CHANGE: CompilerContext.compile now requires type arguments
BREAKING CHANGE: CompilerContext.build_compiled_def no longer returns the remaining type args
BREAKING CHANGE: CustomFunctionDef is no longer a CompiledCallableDef. Added a new CustomMonoFunctionDef that is the result of checking a CustomFunctionDef for some concrete type args
BREAKING CHANGE: RawFunctionDecl.parse now returns an intermediate ParsedFunctionDecl that is turned into a monomorphic CheckedFunctionDecl after checking

BEGIN_COMMIT_OVERRIDE
feat: Monomorphise everything during type checking
END_COMMIT_OVERRIDE

@hugrbot
Copy link
Copy Markdown
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:271: CompilationEngine.get_checked(mono_args):
Parameter was added as required

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

guppylang-internals/src/guppylang_internals/definition/function.py:168: 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:263: 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:288: CompiledFunctionDef.load(globals):
Parameter was removed

guppylang-internals/src/guppylang_internals/definition/function.py:288: CompiledFunctionDef.load(ctx):
Parameter was added as required

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:292: CompiledFunctionDef.compile_call(type_args):
Parameter was removed

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

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

guppylang-internals/src/guppylang_internals/definition/function.py:292: 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__(link_name):
Positional parameter was moved
Details: position: from 7 to 6 (-1)

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

guppylang-internals/src/guppylang_internals/definition/function.py:0: CompiledFunctionDef.__init__(func_def):
Positional parameter was moved
Details: position: from 9 to 8 (-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:312: compile_call(type_args):
Parameter was removed

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

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

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

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:0: OverloadedFunctionDef.load_with_args:
Public object was removed

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)

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:231: CompiledFunctionDecl.compile_call(type_args):
Parameter was removed

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

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

guppylang-internals/src/guppylang_internals/definition/declaration.py:231: 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/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/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:218: 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/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/pytket_circuits.py:0: CompiledPytketDef.load_with_args:
Public object was removed

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

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

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

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

guppylang-internals/src/guppylang_internals/tracing/util.py:14: capture_guppy_errors:
Public object points to a different kind of object:
Old: function
New: class

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:68: MonoGlobalConstId:
Attribute value was changed:
Old: tuple[GlobalConstId, PartiallyMonomorphizedArgs | None]
New: tuple[GlobalConstId, Inst]

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:114: CompilerContext.compile(mono_args):
Parameter was added as required

guppylang-internals/src/guppylang_internals/compiler/core.py:157: 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: require_monomorphization:
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/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:598: TupleType.__init__(preserve):
Parameter 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/subst.py:24: Inst:
Attribute value was changed:
Old: Sequence[Argument]
New: tuple[Argument, ...]

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/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/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:214: check_bb(generic_params):
Parameter was removed

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

guppylang-internals/src/guppylang_internals/checker/core.py:315: Globals.f_locals:
Attribute value was changed:
Old: None
New: frame.f_locals

guppylang-internals/src/guppylang_internals/checker/core.py:316: Globals.f_globals:
Attribute value was changed:
Old: None
New: frame.f_globals

guppylang-internals/src/guppylang_internals/checker/core.py:317: Globals.f_builtins:
Attribute value was changed:
Old: None
New: frame.f_builtins

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


@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 14, 2026

🐰 Bencher Report

Branchfeat/early-mono
TestbedLinux
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
747,210.49 µs
(-19.20%)Baseline: 924,739.51 µs
970,976.48 µs
(76.95%)
tests/benchmarks/test_big_array.py::test_big_array_compile📈 view plot
🚷 view threshold
1,755,073.42 µs
(-14.43%)Baseline: 2,051,093.86 µs
2,153,648.55 µs
(81.49%)
tests/benchmarks/test_big_array.py::test_big_array_executable📈 view plot
🚷 view threshold
7,972,583.50 µs
(-7.24%)Baseline: 8,594,932.32 µs
9,024,678.94 µs
(88.34%)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_check📈 view plot
🚷 view threshold
106,841.41 µs
(-4.59%)Baseline: 111,977.10 µs
117,575.96 µs
(90.87%)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile📈 view plot
🚷 view threshold
203,830.91 µs
(-12.72%)Baseline: 233,546.62 µs
245,223.95 µs
(83.12%)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_executable📈 view plot
🚷 view threshold
922,646.31 µs
(-27.25%)Baseline: 1,268,164.74 µs
1,331,572.98 µs
(69.29%)
tests/benchmarks/test_prelude.py::test_import_guppy📈 view plot
🚷 view threshold
49.51 µs
(-6.16%)Baseline: 52.76 µs
55.40 µs
(89.37%)
🐰 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
Copy Markdown
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
Copy Markdown
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?

Copy link
Copy Markdown
Collaborator Author

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!

Fixed!

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?

Yes, exactly

Are you sure that the refactor to do comptime tracing during checking, isn't something we should do first?

I think the priority is to get this monomorphisation change in first since it's more important for other features. But I would like to clean up tracing soon!

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?

That's hard since we're already in the process of building the graph while tracing. So we would need to leave some holes in the graph and fill them in later. Not sure if that's worth if we're going to change the way tracing works anyways...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we handle the latter by doing some checking during compilation? Factor compile into a call to check (that empties the to-check worklist) and then does compilation, and if we enqueue more stuff to check then call the same check method again? I mean, it only works if we have a good idea when we might need to do more checking, but "when compiling tracing" seems to get us a fair way towards knowing that (and it may be ok just to empty the to-check worklist every time we push anything onto it).

The above might not be that big a change - indeed this comment suggests we have some interleaving between checking/compile already and maybe that could be enough (/nearly)?

But failing that, well ok, maybe we need to just live with the hack until we can get onto the tracing-during-checking thing. In that case let's just put some comment here to that effect.

Oh, and is there an issue for the comptime refactor? Sounds good, but I haven't entirely understood how this will work yet - will the "new" tracing build ParsedFunctionDef/CheckedFunctionDefs etc. containing newly built python AST?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I found an alternative way to get rid of the hack: be more conservative about which places are annotated as capture_guppy_errors. See 00202e9

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh, and is there an issue for the comptime refactor?

Created issue #1592

Sounds good, but I haven't entirely understood how this will work yet - will the "new" tracing build ParsedFunctionDef/CheckedFunctionDefs etc. containing newly built python AST?

Not sure yet - building checked AST would be the obvious way to go imo, but maybe there are alternatives

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Jan 14, 2026

Codecov Report

❌ Patch coverage is 97.00272% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.50%. Comparing base (a5a02f8) to head (1608826).

Files with missing lines Patch % Lines
...ls/src/guppylang_internals/checker/expr_checker.py 92.30% 2 Missing ⚠️
...ls/src/guppylang_internals/checker/stmt_checker.py 75.00% 2 Missing ⚠️
...ang-internals/src/guppylang_internals/tys/const.py 0.00% 2 Missing ⚠️
.../src/guppylang_internals/compiler/expr_compiler.py 93.75% 1 Missing ⚠️
.../guppylang_internals/definition/pytket_circuits.py 80.00% 1 Missing ⚠️
...pylang-internals/src/guppylang_internals/engine.py 98.86% 1 Missing ⚠️
...ylang_internals/std/_internal/compiler/platform.py 0.00% 1 Missing ⚠️
...ylang-internals/src/guppylang_internals/tys/arg.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1441      +/-   ##
==========================================
- Coverage   93.55%   93.50%   -0.05%     
==========================================
  Files         130      130              
  Lines       12436    12397      -39     
==========================================
- Hits        11634    11592      -42     
- Misses        802      805       +3     

☔ 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 thread tests/error/comptime_arg_errors/const_mismatch2.err Outdated
Comment on lines -10 to -14
Note: Parameter `tag` was instantiated to `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaa`

Copy link
Copy Markdown
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
Copy Markdown
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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I'm a bit reluctant to add the monomorphization to every error. Ideally, we'd be able to identify for which errors this context might be useful.

I'm imagining an annotation on types and consts that remembers if they arose from a monomorphic instantiation. Then, the errors could check if they refer to such types and print additional context.

But personally, I'd prefer to leave this as a follow up, what do you think?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think if we can improve the checking of the uninstantiated decl, then the remaining errors (that are only caught when instantiated) should all have the monomorphization in the error message....because they do not occur without it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point, this seems like a good solution! Unfortunately, this won't help in this case since main is not generic and state_result is checked in place instead of being added to the queue...

In the future we could define state_result like this though:

def state_result(tag: str @ comptime, ...):
    assert comptime(len(tag) < 256), "Tag is too long"
    _hugr_state_result(...)

Comptime assertions like this would be a nice feature I think

@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
Copy Markdown
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
Copy Markdown
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

Comment thread guppylang-internals/src/guppylang_internals/checker/cfg_checker.py
f_locals: dict[str, Any]
f_globals: dict[str, Any]
f_builtins: dict[str, Any]
def_id: DefId | None
Copy link
Copy Markdown
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?)

Comment thread guppylang-internals/src/guppylang_internals/checker/expr_checker.py
Comment thread guppylang-internals/src/guppylang_internals/checker/expr_checker.py
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok I was not clear that the keys here were node id's!

Comment thread tests/error/comptime_arg_errors/const_mismatch2.err Outdated
Comment thread tests/error/comptime_arg_errors/const_mismatch2.py
Comment on lines 101 to 105
"""Returns the compiled definitions corresponding to the given ID, along with
the remaining type args that have not been monomorphized away.

Might mutate the current Hugr if this definition has never been compiled before.
"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

May be slightly outdated

@mark-koch
Copy link
Copy Markdown
Collaborator Author

Think about whether we should allow calls to polymorphic decls rather than continue to raise an error - I think this might help avoid source-code breakage later.

On main, we only raise an error for decls that are generic over non-nat constants, most are accepted. With this PR, we now accept all of them. If we really wanted, we could preserve the behaviour of main, but imo it's not worth the complexity (we would need to keep the requires_partial_monomorphization check around). Disallowing all generic decls will be a breaking language change anyways, so I think it's ok to do it all in one go?

@mark-koch mark-koch added this pull request to the merge queue Apr 2, 2026
Merged via the queue into main with commit bde8404 Apr 2, 2026
12 of 16 checks passed
@mark-koch mark-koch deleted the feat/early-mono branch April 2, 2026 14:04
@maximilianruesch maximilianruesch restored the feat/early-mono branch April 2, 2026 14:59
@maximilianruesch maximilianruesch deleted the feat/early-mono branch April 2, 2026 15:01
@github-actions
Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchfeat/early-mono
TestbedLinux
Click to view all benchmark results
Benchmarkhugr_bytesBenchmark Result
bytes x 1e3
(Result Δ%)
Upper Boundary
bytes x 1e3
(Limit %)
hugr_nodesBenchmark Result
nodes x 1e3
(Result Δ%)
Upper Boundary
nodes x 1e3
(Limit %)
tests/benchmarks/test_big_array.py::test_big_array_compile📈 view plot
🚷 view threshold
141.59 x 1e3
(-10.82%)Baseline: 158.77 x 1e3
160.36 x 1e3
(88.29%)
📈 view plot
🚷 view threshold
6.64 x 1e3
(0.00%)Baseline: 6.64 x 1e3
6.71 x 1e3
(99.01%)
tests/benchmarks/test_ctrl_flow.py::test_many_ctrl_flow_compile📈 view plot
🚷 view threshold
25.96 x 1e3
(-5.72%)Baseline: 27.53 x 1e3
27.81 x 1e3
(93.35%)
📈 view plot
🚷 view threshold
1.07 x 1e3
(0.00%)Baseline: 1.07 x 1e3
1.08 x 1e3
(99.01%)
🐰 View full continuous benchmarking report in Bencher

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

5 participants