Skip to content

Commit e4408f7

Browse files
authored
[LQP Meta] Dead function elimination (#190)
* Add dead function analysis so we don't output pretty-printer functions into the parser and vice-versa * Introduce a visitor for target IR * eliminate dead functions * dead function elimination for pretty printers
1 parent 472e343 commit e4408f7

15 files changed

Lines changed: 987 additions & 1615 deletions

File tree

go/src/parser.go

Lines changed: 108 additions & 332 deletions
Large diffs are not rendered by default.

julia/LogicalQueryProtocol/src/parser.jl

Lines changed: 106 additions & 324 deletions
Large diffs are not rendered by default.

python-tools/src/lqp/gen/parser.py

Lines changed: 121 additions & 337 deletions
Large diffs are not rendered by default.

python-tools/src/lqp/gen/pretty.py

Lines changed: 110 additions & 384 deletions
Large diffs are not rendered by default.

python-tools/src/meta/codegen_base.py

Lines changed: 43 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from abc import ABC, abstractmethod
99
from dataclasses import dataclass
10-
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Union
10+
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
1111

1212
from .target import (
1313
TargetExpr, Var, Lit, Symbol, Builtin, NamedFun, NewMessage, EnumValue, OneOf, ListExpr, Call, Lambda, Let,
@@ -64,6 +64,7 @@ class CodeGenerator(ABC):
6464
def __init__(self, proto_messages: Optional[Dict[Tuple[str, str], Any]] = None) -> None:
6565
self.builtin_registry: Dict[str, BuiltinSpec] = {}
6666
self.proto_messages = proto_messages or {}
67+
self._generate_cache: Dict[type, Callable] = {}
6768

6869
@abstractmethod
6970
def escape_keyword(self, name: str) -> str:
@@ -401,81 +402,42 @@ def generate_lines(self, expr: TargetExpr, lines: List[str], indent: str = "") -
401402
Returns the value expression as a string, or None if the expression
402403
returns (i.e., contains a Return node that was executed).
403404
"""
404-
if isinstance(expr, Var):
405-
return self.escape_identifier(expr.name)
405+
t = type(expr)
406+
method = self._generate_cache.get(t)
407+
if method is None:
408+
method = getattr(self, f'_generate_{t.__name__}', None)
409+
if method is None:
410+
raise ValueError(f"Unknown expression type: {t.__name__}")
411+
self._generate_cache[t] = method
412+
return method(expr, lines, indent)
406413

407-
elif isinstance(expr, Lit):
408-
return self.gen_literal(expr.value)
414+
def _generate_Var(self, expr: Var, lines: List[str], indent: str) -> str:
415+
return self.escape_identifier(expr.name)
409416

410-
elif isinstance(expr, Symbol):
411-
return self.gen_symbol(expr.name)
417+
def _generate_Lit(self, expr: Lit, lines: List[str], indent: str) -> str:
418+
return self.gen_literal(expr.value)
412419

413-
elif isinstance(expr, NewMessage):
414-
return self._generate_newmessage(expr, lines, indent)
420+
def _generate_Symbol(self, expr: Symbol, lines: List[str], indent: str) -> str:
421+
return self.gen_symbol(expr.name)
415422

416-
elif isinstance(expr, EnumValue):
417-
return self._generate_enum_value(expr, lines, indent)
423+
def _generate_Builtin(self, expr: Builtin, lines: List[str], indent: str) -> str:
424+
return self.gen_builtin_ref(expr.name)
418425

419-
elif isinstance(expr, Builtin):
420-
return self.gen_builtin_ref(expr.name)
426+
def _generate_NamedFun(self, expr: NamedFun, lines: List[str], indent: str) -> str:
427+
return self.gen_named_fun_ref(expr.name)
421428

422-
elif isinstance(expr, NamedFun):
423-
return self.gen_named_fun_ref(expr.name)
429+
def _generate_PrintNonterminal(self, expr: PrintNonterminal, lines: List[str], indent: str) -> str:
430+
return self.gen_pretty_nonterminal_ref(expr.nonterminal.name)
424431

425-
elif isinstance(expr, PrintNonterminal):
426-
return self.gen_pretty_nonterminal_ref(expr.nonterminal.name)
432+
def _generate_ParseNonterminal(self, expr: ParseNonterminal, lines: List[str], indent: str) -> str:
433+
return self.gen_parse_nonterminal_ref(expr.nonterminal.name)
427434

428-
elif isinstance(expr, ParseNonterminal):
429-
return self.gen_parse_nonterminal_ref(expr.nonterminal.name)
435+
def _generate_GetField(self, expr: GetField, lines: List[str], indent: str) -> str:
436+
obj_code = self.generate_lines(expr.object, lines, indent)
437+
assert obj_code is not None, "GetField object should not contain a return"
438+
return self.gen_field_access(obj_code, expr.field_name)
430439

431-
elif isinstance(expr, OneOf):
432-
return self._generate_oneof(expr, lines, indent)
433-
434-
elif isinstance(expr, ListExpr):
435-
return self._generate_list_expr(expr, lines, indent)
436-
437-
elif isinstance(expr, GetField):
438-
obj_code = self.generate_lines(expr.object, lines, indent)
439-
assert obj_code is not None, "GetField object should not contain a return"
440-
return self.gen_field_access(obj_code, expr.field_name)
441-
442-
elif isinstance(expr, GetElement):
443-
return self._generate_get_element(expr, lines, indent)
444-
445-
elif isinstance(expr, Call):
446-
return self._generate_call(expr, lines, indent)
447-
448-
elif isinstance(expr, Lambda):
449-
return self._generate_lambda(expr, lines, indent)
450-
451-
elif isinstance(expr, Let):
452-
return self._generate_let(expr, lines, indent)
453-
454-
elif isinstance(expr, IfElse):
455-
return self._generate_if_else(expr, lines, indent)
456-
457-
elif isinstance(expr, Seq):
458-
return self._generate_seq(expr, lines, indent)
459-
460-
elif isinstance(expr, While):
461-
return self._generate_while(expr, lines, indent)
462-
463-
elif isinstance(expr, Foreach):
464-
return self._generate_foreach(expr, lines, indent)
465-
466-
elif isinstance(expr, ForeachEnumerated):
467-
return self._generate_foreach_enumerated(expr, lines, indent)
468-
469-
elif isinstance(expr, Assign):
470-
return self._generate_assign(expr, lines, indent)
471-
472-
elif isinstance(expr, Return):
473-
return self._generate_return(expr, lines, indent)
474-
475-
else:
476-
raise ValueError(f"Unknown expression type: {type(expr)}")
477-
478-
def _generate_call(self, expr: Call, lines: List[str], indent: str) -> Optional[str]:
440+
def _generate_Call(self, expr: Call, lines: List[str], indent: str) -> Optional[str]:
479441
"""Generate code for a function call."""
480442
# NewMessage should be handled directly, not wrapped in Call
481443
assert not isinstance(expr.func, NewMessage), \
@@ -567,7 +529,7 @@ def _generate_short_circuit_call(self, op: str, left: TargetExpr, right: TargetE
567529
lines.append(f"{indent}{end}")
568530
return tmp
569531

570-
def _generate_newmessage(self, expr: NewMessage, lines: List[str], indent: str) -> str:
532+
def _generate_NewMessage(self, expr: NewMessage, lines: List[str], indent: str) -> str:
571533
"""Generate code for a NewMessage expression.
572534
573535
Default implementation uses positional constructor args.
@@ -597,7 +559,7 @@ def _generate_newmessage(self, expr: NewMessage, lines: List[str], indent: str)
597559
lines.append(f"{indent}{self.gen_assignment(tmp, call, is_declaration=True)}")
598560
return tmp
599561

600-
def _generate_get_element(self, expr: GetElement, lines: List[str], indent: str) -> str:
562+
def _generate_GetElement(self, expr: GetElement, lines: List[str], indent: str) -> str:
601563
"""Generate code for a GetElement expression.
602564
603565
Default implementation uses 0-based indexing.
@@ -606,11 +568,11 @@ def _generate_get_element(self, expr: GetElement, lines: List[str], indent: str)
606568
tuple_code = self.generate_lines(expr.tuple_expr, lines, indent)
607569
return f"{tuple_code}[{expr.index}]"
608570

609-
def _generate_enum_value(self, expr: EnumValue, lines: List[str], indent: str) -> str:
571+
def _generate_EnumValue(self, expr: EnumValue, lines: List[str], indent: str) -> str:
610572
"""Generate code for an enum value reference."""
611573
return self.gen_enum_value(expr.module, expr.enum_name, expr.value_name)
612574

613-
def _generate_oneof(self, expr: OneOf, lines: List[str], indent: str) -> str:
575+
def _generate_OneOf(self, expr: OneOf, lines: List[str], indent: str) -> str:
614576
"""Generate code for a OneOf expression.
615577
616578
Default implementation treats it as an error since OneOf should only
@@ -619,7 +581,7 @@ def _generate_oneof(self, expr: OneOf, lines: List[str], indent: str) -> str:
619581
"""
620582
raise ValueError(f"OneOf should only appear as arguments to Message constructors: {expr}")
621583

622-
def _generate_list_expr(self, expr: ListExpr, lines: List[str], indent: str) -> str:
584+
def _generate_ListExpr(self, expr: ListExpr, lines: List[str], indent: str) -> str:
623585
"""Generate code for a list expression."""
624586
elements: List[str] = []
625587
for elem in expr.elements:
@@ -628,7 +590,7 @@ def _generate_list_expr(self, expr: ListExpr, lines: List[str], indent: str) ->
628590
elements.append(elem_code)
629591
return self.gen_list_literal(elements, expr.element_type)
630592

631-
def _generate_lambda(self, expr: Lambda, lines: List[str], indent: str) -> str:
593+
def _generate_Lambda(self, expr: Lambda, lines: List[str], indent: str) -> str:
632594
"""Generate code for a lambda expression."""
633595
params = [self.escape_identifier(p.name) for p in expr.params]
634596
f = gensym()
@@ -648,15 +610,15 @@ def _generate_lambda(self, expr: Lambda, lines: List[str], indent: str) -> str:
648610
lines.append(f"{indent}{after}")
649611
return f
650612

651-
def _generate_let(self, expr: Let, lines: List[str], indent: str) -> Optional[str]:
613+
def _generate_Let(self, expr: Let, lines: List[str], indent: str) -> Optional[str]:
652614
"""Generate code for a let binding."""
653615
var_name = self.escape_identifier(expr.var.name)
654616
init_val = self.generate_lines(expr.init, lines, indent)
655617
assert init_val is not None, "Let initializer should not contain a return"
656618
lines.append(f"{indent}{self.gen_assignment(var_name, init_val, is_declaration=True)}")
657619
return self.generate_lines(expr.body, lines, indent)
658620

659-
def _generate_if_else(self, expr: IfElse, lines: List[str], indent: str) -> Optional[str]:
621+
def _generate_IfElse(self, expr: IfElse, lines: List[str], indent: str) -> Optional[str]:
660622
"""Generate code for an if-else expression."""
661623
cond_code = self.generate_lines(expr.condition, lines, indent)
662624
assert cond_code is not None, "If condition should not contain a return"
@@ -754,7 +716,7 @@ def _generate_nil_else_branch(
754716
lines.append(f"{body_indent}{self.gen_assignment(tmp, else_code)}")
755717
return else_code
756718

757-
def _generate_seq(self, expr: Seq, lines: List[str], indent: str) -> Optional[str]:
719+
def _generate_Seq(self, expr: Seq, lines: List[str], indent: str) -> Optional[str]:
758720
"""Generate code for a sequence of expressions.
759721
760722
If any expression returns None (indicating a return statement was executed),
@@ -767,7 +729,7 @@ def _generate_seq(self, expr: Seq, lines: List[str], indent: str) -> Optional[st
767729
break
768730
return result
769731

770-
def _generate_while(self, expr: While, lines: List[str], indent: str) -> str:
732+
def _generate_While(self, expr: While, lines: List[str], indent: str) -> str:
771733
"""Generate code for a while loop."""
772734
m = len(lines)
773735
cond_code = self.generate_lines(expr.condition, lines, indent)
@@ -796,7 +758,7 @@ def _generate_while(self, expr: While, lines: List[str], indent: str) -> str:
796758

797759
return self.gen_none()
798760

799-
def _generate_foreach(self, expr: Foreach, lines: List[str], indent: str) -> str:
761+
def _generate_Foreach(self, expr: Foreach, lines: List[str], indent: str) -> str:
800762
"""Generate code for a foreach loop."""
801763
collection_code = self.generate_lines(expr.collection, lines, indent)
802764
assert collection_code is not None, "Foreach collection should not contain a return"
@@ -812,7 +774,7 @@ def _generate_foreach(self, expr: Foreach, lines: List[str], indent: str) -> str
812774

813775
return self.gen_none()
814776

815-
def _generate_foreach_enumerated(self, expr: ForeachEnumerated, lines: List[str], indent: str) -> str:
777+
def _generate_ForeachEnumerated(self, expr: ForeachEnumerated, lines: List[str], indent: str) -> str:
816778
"""Generate code for a foreach enumerated loop."""
817779
collection_code = self.generate_lines(expr.collection, lines, indent)
818780
assert collection_code is not None, "ForeachEnumerated collection should not contain a return"
@@ -829,15 +791,15 @@ def _generate_foreach_enumerated(self, expr: ForeachEnumerated, lines: List[str]
829791

830792
return self.gen_none()
831793

832-
def _generate_assign(self, expr: Assign, lines: List[str], indent: str) -> str:
794+
def _generate_Assign(self, expr: Assign, lines: List[str], indent: str) -> str:
833795
"""Generate code for an assignment."""
834796
var_name = self.escape_identifier(expr.var.name)
835797
expr_code = self.generate_lines(expr.expr, lines, indent)
836798
assert expr_code is not None, "Assignment expression should not contain a return"
837799
lines.append(f"{indent}{self.gen_assignment(var_name, expr_code)}")
838800
return self.gen_none()
839801

840-
def _generate_return(self, expr: Return, lines: List[str], indent: str) -> None:
802+
def _generate_Return(self, expr: Return, lines: List[str], indent: str) -> None:
841803
"""Generate code for a return statement.
842804
843805
Returns None to indicate that the caller should not add another return

python-tools/src/meta/codegen_go.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ def _generate_nil_else_branch(
334334
"""Go's var declarations zero-initialize, so no else branch needed."""
335335
return self.gen_none()
336336

337-
def _generate_get_element(self, expr: GetElement, lines: List[str], indent: str) -> str:
337+
def _generate_GetElement(self, expr: GetElement, lines: List[str], indent: str) -> str:
338338
"""Go uses 0-based indexing with type assertion for tuple elements."""
339339
tuple_code = self.generate_lines(expr.tuple_expr, lines, indent)
340340
# Add type assertion since tuple elements are interface{}
@@ -367,18 +367,15 @@ def _is_optional_scalar_field(self, expr) -> bool:
367367
inner_go = self.gen_type(expr.field_type.element_type)
368368
return not self._is_nullable_go_type(inner_go)
369369

370-
def generate_lines(self, expr: TargetExpr, lines: List[str], indent: str = "") -> Optional[str]:
371-
from .target import GetField
372-
# For optional scalar proto fields, use direct PascalCase access
373-
# to preserve pointer type (getters strip it).
374-
if isinstance(expr, GetField) and self._is_optional_scalar_field(expr):
370+
def _generate_GetField(self, expr, lines: List[str], indent: str) -> str:
371+
if self._is_optional_scalar_field(expr):
375372
obj_code = self.generate_lines(expr.object, lines, indent)
376373
assert obj_code is not None
377374
pascal_field = to_pascal_case(expr.field_name)
378375
return f"{obj_code}.{pascal_field}"
379-
return super().generate_lines(expr, lines, indent)
376+
return super()._generate_GetField(expr, lines, indent)
380377

381-
def _generate_seq(self, expr: Seq, lines: List[str], indent: str) -> Optional[str]:
378+
def _generate_Seq(self, expr: Seq, lines: List[str], indent: str) -> Optional[str]:
382379
"""Generate Go sequence, suppressing unused variable errors.
383380
384381
In Go, declared-but-unused variables are compile errors. When an
@@ -395,7 +392,7 @@ def _generate_seq(self, expr: Seq, lines: List[str], indent: str) -> Optional[st
395392
lines.append(f"{indent}_ = {result}")
396393
return result
397394

398-
def _generate_newmessage(self, expr: NewMessage, lines: List[str], indent: str) -> str:
395+
def _generate_NewMessage(self, expr: NewMessage, lines: List[str], indent: str) -> str:
399396
"""Generate Go code for NewMessage with fields containing OneOf calls.
400397
401398
In Go protobuf, OneOf fields require wrapping values in the appropriate
@@ -542,7 +539,7 @@ def unwrap_if_option(field_expr, field_value: str) -> Tuple[str, str]:
542539

543540
return tmp
544541

545-
def _generate_call(self, expr: Call, lines: List[str], indent: str) -> Optional[str]:
542+
def _generate_Call(self, expr: Call, lines: List[str], indent: str) -> Optional[str]:
546543
"""Override to handle OneOf, Parse/PrintNonterminal, NamedFun, and option builtins for Go."""
547544
from .target import NamedFun, FunctionType, ListType, BaseType, Builtin, OptionType
548545

@@ -601,7 +598,7 @@ def _generate_call(self, expr: Call, lines: List[str], indent: str) -> Optional[
601598
return tmp
602599

603600
# Fall back to base implementation
604-
return super()._generate_call(expr, lines, indent)
601+
return super()._generate_Call(expr, lines, indent)
605602

606603
def _generate_option_builtin(self, expr: Call, lines: List[str], indent: str) -> Optional[str]:
607604
"""Generate Go code for option-related builtins using pointer/nil idioms."""
@@ -686,17 +683,17 @@ def _generate_option_builtin(self, expr: Call, lines: List[str], indent: str) ->
686683
return f"deref({opt_code}, {default_code})"
687684

688685
# Should not reach here
689-
return super()._generate_call(expr, lines, indent)
686+
return super()._generate_Call(expr, lines, indent)
690687

691-
def _generate_oneof(self, expr: OneOf, lines: List[str], indent: str) -> str:
688+
def _generate_OneOf(self, expr: OneOf, lines: List[str], indent: str) -> str:
692689
"""Generate Go OneOf reference.
693690
694691
OneOf should only appear as the function in Call(OneOf(...), [value]).
695692
This method shouldn't normally be called.
696693
"""
697694
raise ValueError(f"OneOf should only appear in Call(OneOf(...), [value]) pattern: {expr}")
698695

699-
def _generate_return(self, expr, lines: List[str], indent: str) -> None:
696+
def _generate_Return(self, expr, lines: List[str], indent: str) -> None:
700697
"""Generate Go return statement, wrapping with ptr() for Option types when needed."""
701698
from .target import Lit, Call, Builtin
702699

@@ -721,7 +718,7 @@ def _generate_return(self, expr, lines: List[str], indent: str) -> None:
721718
lines.append(f"{indent}{self.gen_return(expr_code)}")
722719
return None
723720

724-
def _generate_assign(self, expr, lines: List[str], indent: str) -> str:
721+
def _generate_Assign(self, expr, lines: List[str], indent: str) -> str:
725722
"""Generate Go assignment, handling type-annotated nil declarations.
726723
727724
In Go, `var_name := nil` is not valid because nil has no type.

0 commit comments

Comments
 (0)