Skip to content

[WIP] Collection of Python/Laurel fixes#735

Draft
tautschnig wants to merge 12 commits intomainfrom
tautschnig/mixed-python-laurel-fixes
Draft

[WIP] Collection of Python/Laurel fixes#735
tautschnig wants to merge 12 commits intomainfrom
tautschnig/mixed-python-laurel-fixes

Conversation

@tautschnig
Copy link
Copy Markdown
Contributor

To be used commit-by-commit.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

tautschnig and others added 12 commits April 1, 2026 22:26
Test 1: x: int = 5; assert x == 5 — passes correctly (0 diagnostics)
Test 2: x: int = None; assert x == 5 — correctly detects None ≠ 5
Test 3: x: int = None; y: int = 10; assert y == 10 — None NOT detected
  because there is no value-dependent assertion on x

This documents the gap: when None is assigned to a typed variable
(e.g., from a dict literal like {"DBSubnetGroupName": None}), the
type annotation is not enforced at the assignment site. The None
value flows through silently unless a downstream assertion happens
to depend on it.

The fix would be to emit type assertions at assignment sites when
the declared type is concrete (str, int, bool, etc.) and the RHS
could be None.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When a Python variable has a concrete type annotation (int, str, bool,
float) and is assigned a value, emit an assertion that the value matches
the declared type. For example:

    x: int = None  →  assert Any..isfrom_int(x)

This catches the common bug pattern where None is assigned to a typed
variable (e.g., {"DBSubnetGroupName": None} in rds_instance_creator),
which would cause a runtime TypeError when the value is used as the
declared type.

The assertion is emitted at both the AnnAssign handler (x: int = expr)
and the translateAssign fallback path.

Tests:
- Test 1: x: int = 5; assert x == 5 — passes (0 diagnostics)
- Test 2: x: int = None; assert x == 5 — 2 diagnostics (value + type)
- Test 3: x: int = None; y: int = 10; assert y == 10 — 1 diagnostic
  (type assertion catches None-for-int even without value assertion)

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When **dict is expanded against a function signature, emit type
assertions for each parameter with a concrete type (int, str, bool,
float): if the key is present in the dict (DictStrAny_contains),
the value must match the declared type (Any..isfrom_<type>).

This catches the bug pattern from rds_instance_creator where
{"DBSubnetGroupName": None} is unpacked via ** to a function
expecting a str parameter.

Tests 5 and 6 document future work (negative indexing bounds check
and len() on non-iterable types).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When translating xs[-n] (negative integer subscript), emit
assert(len(xs) >= n) before the access. This catches IndexError
from negative indexing on empty or too-short lists.

For example, response["Datapoints"][-1] on an empty Datapoints list
would trigger the assertion.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When len() is called on a variable whose type is a Composite class
(e.g., DynamoDB Table), emit a user error diagnostic since Composite
types do not support __len__ unless explicitly defined.

This catches the bug in clear_duplicate_dynamodb_entries where
len(table) is called on a DynamoDB Table resource.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Three fixes to resolve instance method calls on variables whose type
is a PySpec-defined composite class (e.g., service clients created via
factory dispatch):

1. FilterPrelude: include Type@method staticProcedures when the parent
   Composite type is reachable, so instance methods survive filtering.

2. refineFunctionCallExpr: reverse-lookup the unprefixed type alias
   when the variable type is a prefixed Laurel name, so that method
   names match the importedSymbols entries.

3. funcDecl lookup: also try the Laurel-internal procedure name when
   looking up function signatures for kwargs expansion.

Without these fixes, instance method calls on typed variables silently
became Holes (unmodeled) because the method name resolution failed at
multiple layers.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
specArgToFuncDeclArg was hardcoding "Any" for all parameter types,
discarding the original Smithy type information (str, int, bool, float).
This prevented dict-unpacking type assertions from knowing the expected
types, so passing None for a typed parameter was not detected.

Fix: extract the concrete type name from the SpecType atoms and use it
in the PythonFunctionDecl args. This enables the dict-unpacking type
assertion (implies(DictStrAny_contains, isfrom_<type>)) to fire when
a dict literal contains None for a typed parameter.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When a procedure accepts **kwargs with a TypedDict schema, validate
that named keyword arguments match known parameter or kwargs field
names. Previously, unknown kwargs keys were silently accepted when
the procedure had a kwargs parameter.

This catches misspelled parameter names at the call site.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
All composite types defined in PySpec modules are now treated as
exhaustive, meaning calls to methods not defined in the spec are
flagged as user errors ("Unknown method"). Previously, only
explicitly marked exhaustive types were checked.

This catches calls to non-existent or misspelled methods on typed
service client variables.

Note: exhaustiveness is applied via typeAliases (PySpec types only),
not to user-defined classes.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When a function is called with named keyword arguments (not **dict
expansion), emit type assertions checking that each argument matches
the declared parameter type (int, str, bool, float). Only asserts
when the source expression is a dict/list literal or a variable with
a known dict/list annotation, to avoid false positives on Any-typed
expressions.

Constructor calls (composite types) are excluded since they use the
New + __init__ translation path. Also preserves dict/list type
annotations in variableTypes (previously erased to Any).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Previously, translated method bodies were explicitly replaced with
opaque stubs (assume false). Now the translated bodies are preserved,
enabling bug detection inside class methods.

Also includes two supporting fixes:
- Pre-collect top-level function signatures and userFunctions before
  class translation so method bodies can reference them.
- Fix Laurel-to-Core translator to check isFunction for multi-target
  assignments, preventing function calls (e.g. PAdd) from being
  incorrectly emitted as procedure calls.

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
When the DDM-to-CST transform encounters an unknown unary operation
(e.g. the Core built-in "const" for constant maps), register it as
a free variable and emit a function application instead of logging
an error and substituting a boolean NOT.

This fixes "unary op: const" errors that occurred when class method
bodies referenced type hierarchy maps (which use const() for default
map values).

Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
@tautschnig tautschnig self-assigned this Apr 1, 2026
@github-actions
Copy link
Copy Markdown

👋 Hi, @tautschnig,
I detected conflicts against the base branch 🙊
You'll want to sync 🔄 your branch with upstream!


This message is automatically generated by prince-chrismc/label-merge-conflicts-action so don't hesitate to report issues/improvements there.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant