Conversation
Wrapping PRIMA surfaced four defects in the callback path. A dummy procedure's interface name kept the casefolded key used to match it, so a generated .pyi annotated `procedure(OBJ)` as `obj` while importing `OBJ` and could not be rebuilt from its own contract. The parser now keeps the declared spelling and normalizes case at each comparison. An assumed-shape array in a callback prototype emitted the plan's runtime extent marker as Fortran text, `dimension(::Strided)`. The bridge now lowers a runtime extent to an assumed-shape dummy and measures the contiguous call-local copy from it. An array result has no caller descriptor to measure and reports that directly instead. An abstract interface imported from another file did not resolve during single-file conversion, so `generate --pyi a.f90 b.f90` degraded the dummy to an opaque placeholder. Resolution now matches multi-file builds, and an interface that no supplied source declares is reported by name. An `intent(out)` primitive scalar was projected as an independent value. Python has no writable scalar, so the write was silently discarded and the native caller read uninitialized memory. Such a dummy now reaches Python as rank-zero storage, and the value spelling is a policy error naming the replacement. A prototype describes a native callback interface, so it still mirrors the native argument list; `@native_call` projection remains available in the contract for a return-oriented callable. Plan validation covers the storage projection, which the scalar rule previously skipped. Callback parameters now document the exact callable they expect, generated from the same completed prototype the trampoline is built from, so the documented signature cannot drift from the real ABI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Fortran permits a dummy with no declared INTENT to be both read and
modified, but a primitive scalar callback dummy without one was projected
as an independent value and copied in only, so a write by the Python
callable was discarded.
Semantic normalization now records rank-zero storage for such a dummy, and
the callback transfer direction follows that completed storage rather than
re-deriving copy-in from the absent intent. The declaration itself is
unchanged: no intent is synthesized into the semantic origin or the
generated Fortran interface, so the contract records the absence by
carrying no direction wrapper.
real(8), intent(in) :: f -> f: In(Addr(Float64)) copy-in
real(8), intent(out) :: f -> f: Out(Float64[()]) copy-out
real(8), intent(inout) :: f -> f: InOut(Float64[()]) copy-in/out
real(8) :: f -> f: Float64[()] copy-in/out
--assume-intent-in-scalars continues to elect which default an undeclared
intent receives, narrowing that last row to the input-only projection
without giving the dummy a direction it never declared.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
The callback key rules kept a sentence stating that every primitive scalar callback argument arrives as an independent NumPy scalar value, which contradicted the writable-storage rule documented directly below it. One rule now covers both projections. The undeclared-intent regression asserted the generated contract text and then rebuilt from the Fortran source, so nothing proved the bare Float64[()] spelling survived being read back. It now builds through that generated contract and runs the callback, covering source, contract, policy, codegen and runtime in one pass; the shared helper takes an optional fixture package so a round trip needs no checked-in contract. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
An abstract interface imported from another module was converted in the scope of the module that imported it. An interface body is written where it is declared, so a derived type it names belongs to the declaring module; a consumer that imported only the interface attributed that type to itself and failed with no completed wrapper type definition for a type it never declares. Interface lookup now carries the declaring module, the prototype's dummies convert in that module's context, and a type local to the declaring module records it as the origin. Resolution also stopped at module-level imports. A `use` inside a single procedure, a standalone procedure's own imports, and an interface re-exported through another module now all resolve, following a chain of any length to the module that declares it. File, project and per-file CLI conversion share one resolver instead of each carrying its own lookup, and contract reconciliation follows a re-export so a prototype imported from a module that only republishes it still binds to its declaration. A contract now also imports a prototype it references but never declares, which a procedure-local `use` previously left as a free name. Callback docstrings state each array argument's rank and extents, taken from the completed transfer plan, and every generated docstring spells a runtime extent the way the contract spells it rather than exposing the internal marker. Regression coverage: imported interfaces owning derived types, the three resolution routes, multi-file `generate --pyi` through parse and build including a renamed import, rank-two assumed-shape callbacks, writable scalar storage on the bridge-free direct bind(C) route, and the `--assume-intent-in-scalars` override end to end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Three gaps remained in how an imported callback interface carries its source facts. Declaring-module ownership was recorded only while iterating dummies, so a function interface returning a type its own module declares attributed that type to the consuming module and failed to build. The helper now takes a semantic type and its declaration rather than an argument, and both the dummies and the result use it. Binding a prototype reference from a contract records the same origin, so the generated `.pyi` builds too. A renamed import kept only the local spelling, so the reference named an interface the declaring module never defines and the contract imported a name that does not exist there. The resolver now carries the local spelling beside the declaring signature, through any number of re-export hops, and the reference records both. A reference differing from the declaration only in case is the same interface, so it is spelled canonically rather than binding a second name. Following a re-export ignored Fortran accessibility, so a module that imported an interface privately still appeared to publish it. Reaching names from another module now applies that module's own visibility rules, at every hop; a module still sees its own private interfaces. Not addressed here: resolving a cross-module derived type through the runtime namespace. A wrapper looks the type up on the module owning the function rather than the one declaring the type, which also affects an ordinary function returning an imported type and predates this branch. The callback-result regression therefore asserts the build, not a call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Two bridge diagnostics interpolated extent expressions straight from the plan, so rejecting a strided callback array result reported extents ['::Strided'] -- the explicit step the IR stores -- rather than the shorthand the author wrote. `Strided` is a public contract name, so `T[::Strided]` and `T[::]` are two spellings of one contract while `T[:]` is the distinct contiguous one. The shorthand now has a single owner beside the marker set it belongs to, and the docstring builder reads it from there instead of keeping a private copy under a name that implied the explicit form was internal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Carrying the declaring module's classes into prototype binding pushed reconcile_external_type_refs to complexity 21, over the staged limit of 20. The prototype branch moves to its own function, which also lets the module name candidates reuse the helper the re-export index already uses. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
`T[::]` already spells a strided axis and `T[:]` a contiguous one, so the explicit `T[::Strided]` and `T[0:n:Strided]` forms were a second way to write contracts that already had one. The docs described `Strided` as a compatibility spelling for an older form and told authors to use the short one; it is now gone rather than carried. The step position spelled nothing else, so a value there is refused with a message naming the spelling to use instead. Without that check the removed form would still have parsed: its text happens to match the marker the IR carries for a strided axis, so dropping the contract name alone left it working for anyone who did not import the name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
The IR named a strided axis after a contract name that no longer exists, so every layer that showed one to a reader translated it back: the `.pyi` printer, the docstring builder and two bridge diagnostics each converted the token to `::`. Producing `::` directly removes the translation and the mismatch behind it. The axis mode had been read from the word itself, so that rule moves beside the marker set it belongs to and states what actually marks a strided axis: a trailing empty step, with bounds (`lower:upper:`) or without (`::`). Six sites re-declared the runtime marker sets as literals; they now read the shared ones. The absence assertions in the callback planning tests went with the token -- `::` is Fortran's declaration separator, so its absence from generated source says nothing, and the positive spellings beside them already prove the lowering. `prik semantics` output changes with the IR, so its two expected payloads are regenerated. Contracts, docstrings and generated sources are byte for byte unchanged, having already printed the contract spelling. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A reference reached through renaming re-exports followed the module provenance back to the declaration but kept the alias it was last bound to, so the metadata claimed the declaring module defines a name it never does: name MID against origin_module A, where A declares OBJ. The declaration names the symbol, so _bind_prototype_reference takes it from the resolved prototype instead of from a caller that may only hold an intermediate alias. The one caller that already passed the declaring name is unaffected, and the caller that could not know it no longer has to. A rename and a same-name re-export were each covered; their combination was not, which is where this sat. Both routes are now covered: the contract chain through reconciliation, and a Fortran chain generated to contracts, built and called. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Fortran lets a scope build a generic interface from several blocks, each contributing specifics. PRIMA does this under preprocessor guards, adding kind-specific procedures only for the precisions a build supports, so `huge_value` arrives as two blocks that gfortran accepts and the parser rejected as a duplicate declaration. Blocks naming one generic in one scope now merge into a single interface carrying every entry in declaration order, keyed by module so two modules in a file keep their own. Abstract and unnamed blocks are never generics and are untouched, and the duplicate check still holds for every other unit kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A local interface block repeating a use-associated generic name extends that generic; it does not replace it. PRIK resolved only the specifics a module declared, so an extending module published a generic missing everything it inherited and rejected calls gfortran accepts. The importing scope now resolves the specifics that reached it through the import as well as its own, following the import chain. Accumulation stays one-directional, as Fortran requires: the declaring module gains nothing from a module that extends it later. An inherited specific joins the importing module privately, since the import bound the generic name and not the specific's own, so it is reachable only through the generic. Two identities had been inferred from an overload's first specific, which only holds while one module owns them all. A generic now records the scope that declares it, so an extended generic is published by the extending module rather than the one it inherited from, and a module generic addresses each candidate by the scope owning that procedure so an inherited one stays findable. Class-bound overloads are addressed by their class as before, which owns every candidate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A module that names an imported procedure in a `public` statement means it to be part of its own interface, but PRIK dropped the module entirely: a facade that only re-exports reached Python as nothing at all, so callers had to reach past it into the modules it was hiding. The name is published without repeating the declaration. A re-export names an existing wrapper rather than adding one, so the plan carries an alias binding the name to the callable its declaring namespace already exposes. One wrapper is generated, the contract keeps spelling the re-export as the import it already was, and `facade.proc is home.proc` holds. Naming the entity is what states the intent. A name public only because the module default is public carries no such statement, and mirroring that would republish everything a module happens to import under every namespace that imports it, so those are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A declaration expression naming an imported specification function recorded the import spelling as the function's native scope, so a relative sibling import left the scope as `.extent_helpers` where Fortran names the module `extent_helpers`. Imported type identities were already normalised; this applies the same rule to declaration callables, and fixes the namespace branch beside it, which split on the leading dot and produced an empty name. Assertions across the Fortran, C and round-trip suites pinned the previous absolute spelling and now expect the relative one. The C frontend emits sibling header imports through the same printer, so those move with it. Found by running the full suite, which the relative-import change had not been through: five Fortran and three C failures, one of them this defect and the rest pinned spellings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A `use` statement replaced any earlier import of the same module instead of adding to it, so a scope naming one module across several statements kept only the last. PRIMA splits iso_fortran_env across three lines, so `DP => REAL64` was dropped and `real(RP)` could not be resolved from source: the kind reached the compiler probe as a project name the probe cannot see. With every import kept, the existing project symbol table resolves `RP` to `REAL64` and `IK` to `kind(0)`, which the probe evaluates as the intrinsic expressions they are. A bare `use` imports everything, so it absorbs any list beside it rather than being narrowed by one. Ordering a procedure's outputs also compared an unplaced position against placed ones and raised a comparison error. An output with no position is what the check exists to catch, so it is reported as an unsupported wrapper policy instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A derived type may build one type-bound generic from several `generic ::`
statements, each contributing specifics. The parser recorded one binding
per statement, so a type declaring
generic :: area => area_int
generic :: area => area_real
carried two bindings both named `area`. Only the first reached dispatch,
and calling the generic with the argument types of any later statement
raised `no matching overload` at runtime. The single-statement spelling
worked, so whether a call resolved depended on how the source was written.
The generated contract hid this: its printer renders same-named overload
sets as consecutive `@overload` defs, which is what Python wants, so both
spellings produced byte-identical `.pyi` text and the loss surfaced only
in the built extension.
Merge the statements where the module-level generic interface blocks are
already merged. The key ignores case and internal spacing so a defined
operator merges across `operator(+)` and `operator (+)`. Attributes come
from the first statement: the standard requires every statement for one
binding to declare the same accessibility.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
An interface body types its own dummies, and the kind it names may come from a `use` written inside that body -- no module variable or module procedure declares it. The variable-context walk visited a module's variables, procedures and derived types but never its interfaces, so those dummies contributed no target-probe requirement and the conversion later raised on a storage fact nothing had measured. The two input routes disagreed as a result: `generate --pyi` failed with `Unsupported Fortran semantic type for variable 'nf': integer(kind=kind(0))` on sources that `build_fortran_extension` accepted, because a wrapper build's larger parsed set happened to raise the same requirement elsewhere. Walk the interfaces a file or module declares, and report the variables of the bodies they hold. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
An overload declaration names a specific and restates its public signature. The projection that shapes that signature lives on the specific -- a declaration carrying `native_call` is rejected outright -- so the declaration can only spell what the projection leaves visible. The comparison read the native form instead, and rejected two shapes a generated contract routinely holds. An output argument projected into a result kept the write-through its argument passing states. Whether the call writes through a dummy is not part of a result type, and the comparison already read ownership from the declaration for that reason; its storage mutability now follows. A native scalar descriptor result kept its descriptor topology, which only a `native_call` result wrapper can name. The contract printer already strips it when emitting such a result as a nullable value, and the comparison now expects what the printer writes. Reading that annotation back needed the `| None` unwrapped as well, which until now happened only for a slot some projection marked nullable. The effect was a contract the same tool refused to read back: a generic over `intent(out)` allocatable arguments, such as an allocation helper, failed on `safealloc` against its first specific. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A source-derived contract declares its entities under Python names, so a Fortran entity kept in capitals is declared lower case with its source spelling recorded beside it. Imports were written straight from the parser's `use` mapping instead, leaving a contract that defines `ik` imported as `IK` -- a name nothing defines, which failed when the package was loaded back. A prototype is the exception. It keeps the spelling its own contract declares, because an annotation naming it is written the same way, so an import binding one keeps that spelling too. Which names those are is a fact about the contracts that declare them, not the one reading them: a module re-exporting a prototype references it nowhere in its own body. The stub emitter already holds every module it renders, so it collects the prototypes they declare and tells each module before any of them writes an import. Either spelling in a renamed import identifies a prototype -- the source names what the dependency declares, the target what the importer calls it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A contract is written to be edited, and the name a declaration states is what Python should call the entity. `SourceName` was read the other way round: the source spelling replaced the declared name, so renaming a variable exported the native spelling and dropped the edit entirely. It records the native entity now, exactly as `bind` does for a callable, and the declared name stands. A source name inside `Final[...]` reaches its declaration as well, where the reader looked only through a bare `Annotated` and dropped it. Generated contracts were caught by this too. A Fortran entity Python cannot spell is declared under a name that it can -- `lambda` becomes `lambda_` -- and reading that back installed the unusable spelling, so the declaration the contract stated was unreachable. A class may state a native type through `bind`, which was refused outright, leaving a derived type locked to a name its Fortran type also answers to. Policy already read `native_name or name`, so only the refusal and the reference lookup had to change: an imported reference names a type the way its declaring contract writes it, and resolving it searches that module alone, never a type of the same name elsewhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Fortran names entities without regard to case, so writing a capitalized `IK` as `ik` renames nothing -- the generated Fortran reaches it either way. Every such declaration nevertheless carried a `SourceName` or `@bind` stating the capitals back, which said nothing the declaration did not already say. Across one real library's contracts that was 60 annotations, none of them load-bearing. The naming policy already drew this line: `normalize_public_name` reports `needs_fix` against the casefolded source, so a pure case change is deliberately not a fix. The printer compared the spellings exactly instead and never consulted it. A name Python cannot hold as written keeps its original: a keyword, a character an identifier cannot carry, a name a collision moved aside. So does every name from a source language that is case-sensitive, where the spellings are still compared as written. A renamed class now states its native type, so the rename survives regeneration. A C struct keeps its own representation rules, which spell `struct node` without a decorator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Publishing an imported name says this module means it to be part of its own interface. What that reaches at runtime depends on what the name declares, and every explicitly-public import was treated the same way. A module publishing an imported callback prototype states where a signature comes from, and a signature is not an object Python holds, so the alias reached for an attribute of a module exporting nothing and the build stopped on a namespace that does not exist. Each re-export now records what it publishes, read from the module declaring it. A procedure and a derived type reach Python as one exported object and become aliases; a prototype, a module variable whose state stays live, and a generic keep to the semantic and contract-import paths already carrying them. An alias also binds a Python attribute, which a Fortran spelling is not. The declaration supplies the name its namespace actually published, so a procedure written in capitals is reached under the name it was exported as, and one rule serves the source and contract routes alike. A plain `use` carries every public name of the module it reads, so a name published without being declared here is one of them. The `public` statement says which, and an origin two such modules could supply stays unresolved rather than guessed. A `use` that publishes nothing still re-exports nothing. Two further names were read as though a spelling identified an entity on its own. A generic built from several blocks merged on the module rather than the scope declaring it, so two procedures' local interfaces of one name became a single generic answering both. A contract wrote an overload's target and a prototype import in source spelling, naming a declaration the contract does not hold and forcing one module's prototype spelling onto every module using that name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
…ontract states Four corrections to the re-export and naming work. A name may be published by a module that was published a name in turn, and reading only the module a `use` names stopped at the first hop. The entity then looked like nothing at all and its re-export was dropped, so a twice-published procedure reached Python through no namespace. Each hop is followed now, through renames and plain `use` alike, until the declaration itself is reached; a cycle ends the search rather than circling it. A contract's imports were all read as exports, so an import written to express a declaration became a Python name after a source-to-contract round trip and the two builds disagreed about what a module publishes. A re-export is stated by aliasing the name to itself, the way a stub marks anything it re-exports, and a plain import states only what the contract needed to name. Both still reach the export tree, because a contract reading from this one has to resolve what it names; only the published ones become attributes. A package entry contract is the exception it has always been: it declares little and exists to choose a surface, so the names it imports are the ones it means to publish. A generic declared inside a procedure belongs to that procedure. The parser stopped merging two such blocks but reported them as the module's own, so later stages read them as module generics of one name. The declaring scope is recorded and module generics are read from module scope alone. Two names were still derived rather than read. A prototype identity was matched exactly, so a `use` differing only in case asked for a spelling no contract declares; it is matched without regard to case and answers with the spelling its contract uses. An import and an overload target were normalized independently of the module that named the declaration, which a collision can move aside: a module holding `lambda` and `lambda_` publishes `lambda_` and `lambda__2`, and publishing the second silently bound the first. Each module is named once before any import is written, and a source spelling identifies a declaration ahead of a published one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
An import cannot say whether a name is needed to express a declaration or meant to be published. `use shapes_mod, only : crate => box` reads the same whether the rename avoids a collision or publishes the type under a new name, so no import spelling can carry the difference. A contract now closes with `__all__`, naming its whole public surface. PRIK writes what the source publishes: a module's own public declarations, and any imported name a `public` statement names. The list exists to be edited. Removing a name leaves the entity declared and reachable from other contracts while it stops reaching Python; adding an imported one publishes it here, including a name imported only to express a declaration; an empty list publishes nothing. A contract stating no list has named no surface, so everything it reaches is published, which is what an entry contract selecting from its package has always meant. Every name reaches the export tree either way, because a contract reading from this one still has to resolve what it names. Only a published name becomes an attribute. Names that reach Python by other means keep to them: a sub-namespace attaches the package tree rather than binding an entity, and an intrinsic module has no contract to read a name from. A re-exported procedure also binds the callable its declaring module exported rather than being wrapped again, so a contract build gives the same object a source build does, renamed re-exports included. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A contract's `__all__` is its public surface, and a sub-namespace is part of that surface: `pkg.solver_mod` is an attribute like any other. It was exempt, so an entry naming one module still exposed every module it imported, and a list that read as a selection pruned nothing. The exemption is gone rather than made conditional, and a generated entry states the modules it imports so there is something to take off. Leaving one off keeps the package from exposing it; stating no list still publishes everything the contract reaches. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Three corrections to the export surface, and one wording change. A declaration published from several namespaces was owned by whichever namespace the entry contract reached first. An entry composes a package by importing from it, and the order it does so says nothing about where anything is declared, so a facade listed before the module it reads from became the owner of a procedure it only republishes -- and the wrapper went to the wrong namespace while the identity assertion still held. Ownership now comes from the contract declaring the entity: each contract's own namespace is read from the export tree, and every other publication aliases what that one owns. A wildcard import took every name its dependency held, including names that dependency's `__all__` withheld. It reads the published surface now. A withheld name stays in the tree and reachable by asking for it, because a contract may need one to express a declaration or mean to publish it itself; only what a dependency publishes answers to `*`. The contract route could also publish what a source build cannot. A module variable holds state that stays live where it is declared and a generic is a dispatch surface rather than one object, yet attaching either to a second namespace gave a contract build an attribute the source build never exposes. A generated contract states neither, and a contract asking for one is refused by name rather than quietly differing. `__all__` states a contract's public symbol surface, which is not the same as the set of runtime objects: a prototype belongs on that list and names a callback signature rather than anything the extension exposes. The reference documents what each kind of published name becomes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
The C route already selects a public surface while reading source, and a semantic contract already states one in __all__. Nothing connected them, so --export-symbols read as a private include-exposure knob and a generated contract's __all__ read as something separate PRIK happened to write. Name them as the same statement in two places: the allowlist selects the source-side public surface, and generating a contract records the Python names it publishes in __all__. After that the contract is authoritative -- edit the list, not the allowlist, which a contract build rejects. The two lists live in different naming domains, the file naming native C identifiers and __all__ naming what reaches Python, so the docs say that rather than implying the spellings are interchangeable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
`_parse_use_statement()` recognized `only :` and then threw that fact away, so
a scope's import table could not tell
use m, only : p => q carries p, and nothing else
use m, p => q carries p, and everything else m publishes
apart: both left one mapping, and every reader took a non-empty mapping list to
mean the `use` was narrowed. A module renaming one name therefore lost access
to every other name its module offered, and route collection saw no wildcard
where the language provides one.
Record it on the mapping, where the reading that produced it belongs, and read
it in the three places that ask what a `use` carries. A rename also makes the
entity reachable only by its local name, so the carried set excludes the name
it renamed away.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Merging the generics a module reaches made specific procedures collide.
`_inherited_generic_specifics()` keyed them by bare name, and
`_resolve_overload_targets()` looked them up the same way, so two contributors
each declaring
interface convert
module procedure to_value
end interface
produced one candidate: the second was taken for the first and dropped, and
the merged generic silently lost a signature it must dispatch over.
A specific is identified by the module declaring it together with the name that
module gives it. `_SpecificProcedure` carries that pair, the inherited, inline,
own and type-bound lookups are all keyed by it, and a target names which
declaration it means rather than which spelling.
A contract then has to write two declarations spelled alike, so the emission
records what it named each one -- by scope, not by spelling -- and an overload
target reads that record. Without it both dispatchers named the first
declaration. A class member is named inside its class, so the record covers
module-level declarations only, exactly as the published-name record does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Settling a procedure-local prototype's contract spelling reserved the module's
declared names first, so a suggestion could not take one. A use-associated name
binds in the module too, and the contract writes an import for it, but it was
not held -- so a module writing
use helper_mod, only : first_cb
whose contained procedure `first` declares `cb` produced a contract where
from .helper_mod import first_cb
@prototype
def first_cb(...)
bind the same name, the prototype shadowing the import.
Hold what the module imports beside what it declares. The prototype takes the
next spelling instead, and the annotation naming it follows, because both read
the one settled name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Two readings in export completion settled a decision instead of reading it. `__all__` was compared case-insensitively. A contract is Python, where `Foo` and `foo` are different names, so a list naming `Foo` beside a declaration written `foo` names something the module does not define -- and publishing `foo` for it invents a surface the contract never stated. And a declaration's export list was tested for truth, so an absent key and an explicit empty list were treated alike. They mean opposite things: no key means no stage has projected the declaration yet, and the default publication applies; an empty list means a stage decided it publishes nowhere, which substituting that default reverses. `_apply_source_python_exports()` writes exactly that empty list for a private declaration, so the two were only ever kept apart by a second guard reaching the same conclusion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Identifying a specific by its declaring module left `_module_overload_sets()` one branch over the staged complexity limit. The rebinding it does for a constructor and for an ordinary generic is the same loop written twice, so it becomes one helper: each private specific is bound through the public name that reaches it -- the generic, or the type name for a constructor. No behavior changes; the two call sites already did exactly this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
`dict[str, list[FortranUseMapping]]` had run out of room. An empty list meant "imports everything", so `use m, only :` -- valid syntax that imports nothing -- could not be told from `use m`. `_record_use_mappings()` resolved the overlap by letting a bare `use` replace whatever an earlier statement listed, so `use m, only : p => q` followed by `use m` lost `p` altogether, though the language reads the two together. And putting `only` on each mapping, as the previous commit did, could not record a statement that has no mappings at all. Keep what the source says: `FortranUseStatement(module, only, mappings)`, one per statement, appended rather than merged. `FortranUseAssociation.of()` is the single reading of them -- what a scope sees under which name -- and every consumer asks it instead of deciding for itself what an empty list or a rename means. That deletes the interpretations rather than adding to them: `_carries_every_public_name()` and `_renamed_away_names()` are gone, and the `if not mappings` branches in route collection, public names, callback interfaces, declaration-call resolution, derived-type origins, contract imports and compile-time symbols are replaced by `imports_all` and `carried()`. Those last five had never been taught about non-`only` renames at all, so `use kinds, wp => rk` no longer loses the rest of what `kinds` offers. `fortran2ir.py` and `parser.py` both end up shorter than before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Making a specific's identity structural left `_bound_overload_sets()` passing
its now tuple-keyed lookup to two helpers that still asked for a bare name.
`_apply_assignment_projection_to_originals()` therefore found nothing, so a
type-bound
generic :: assignment(=) => assign_value
projected its bound object as the result on the generic's candidate while
`assign_value` itself kept the unprojected signature -- the same call behaving
differently through the two names. The module-level path happened to work only
because it was handed the old string-keyed map.
Finish the migration rather than teach the helpers two key shapes. One reading,
`_declared_specific()`, derives a specific's identity from the declaration it
came from, and both helpers and both call sites use it. Nothing is left that
has to guess how its lookup is keyed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Passing tests say a bug is gone; they do not say the code got simpler. This branch has reached the size where local patches start working against the architecture, so record the criterion we are now reviewing against: a fix either deletes a way of deciding or it does not land. The rule names the shapes that keep producing these bugs -- a record that has to mean several things, a lookup reached by two key shapes, a completed decision ambiguous with an absent one, a consumer special-casing what a plan should have decided -- and says to replace the structure rather than widen it. Introducing a record or a small class is the preferred move where it lets a reader see the rule in one place; the condition is that it deletes what it replaces, since moving or wrapping leaves every existing reader standing. It also says to keep the accumulated regressions while doing that. They are the specification of what PRIK supports, and simplifying by dropping cases is not simplifying. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
`uses` was still a dictionary keyed by module name, which left three problems that the statement record alone could not fix. The key repeated `statement.module`, and its values were mutable lists a procedure inherited by sharing: `proc_state.uses.update(module.uses)` copies the dictionary and not the lists, so a `use dep, only : y` written inside a contained procedure appended to the list the module itself held. A procedure-local import therefore reached module accessibility and re-export analysis. Module names are also case-insensitive, so `use DEP` and `use dep` were held under separate keys although the language reads them as one use. And `FortranUseAssociation.carried()` returned one entity per local name. Fortran does not promise that: `use dep, x => y` where `dep` also publishes `x` makes two entities reach `x`, and the second silently won. So `uses` becomes `list[FortranUseStatement]`, frozen with tuple mappings, appended rather than merged; a scope inherits by concatenation. `ScopeUses` groups them case-insensitively and answers the two questions consumers have -- which routes reach a local name, and which names a scope reaches. It reports routes rather than choosing between them, because whether two routes are an ambiguity or a set of contributors is a question about the entities, which the stage holding them already decides. Deleted rather than moved: `_record_use_mappings()`, `FortranUseAssociation` and its `carried()`, the parent-diffing branch of `_procedure_local_uses()`, `_fortran_owner_used_modules()` in the build, and the remaining per-consumer `imports_all` / `mappings` walks in route collection, public names, callback interfaces, contract imports, declaration calls and derived-type origins. `used_module_names()` is now the one reading of what a scope depends on, shared by project indexing, file ordering and compile batching -- which had been three readings, and which the aliasing bug had been quietly propping up. Existing files lose 105 lines; the resolver adds 130 in one place. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Naming a contract's declarations was decided in four places: export policy chose published names, the printer allocated the rest while rendering and kept `settled_names`, `published_names`, `published_specifics`, `reexport_names` and `class_python_names` to remember what it had chosen, class-surface policy ran its own `NamingPolicy` for members, and `published_names()` rendered a whole module just to learn what it would be called. Those four could disagree, and every per-stage test still passed. `_complete_contract_names()` now records a `CONTRACT_NAME_METADATA` on every declaration -- published or withheld, class members and nested classes included -- plus the spelling each overload target names. The printer and class-surface construction read it through `completed_contract_name()`, and the printer keeps no naming state beyond the purely textual aliases for `prik.contracts` imports. That reader sits beside its key in `semantics/models.py`, because a printer may not import policy. Finishing it surfaced four problems the old arrangement had hidden: - A build folds every source module into one, so a single root ledger made four facades each re-exporting `scale_value` collide. Each name is now held where its own authority places it: a published declaration in its completed export namespace, a re-export in its publisher's, a withheld declaration in the file being completed. A declaration's native module is not that file -- a facade carries its generic's inherited specifics -- so it is not used. - A user class named `Vector` was printed as `prik.contracts.Vector`. The old code avoided it only because the semantic type still carried the lowercase Fortran name; a completed spelling is now a declaration, never a symbol. - One procedure can be declared at module level and as the method binding it, and the `.pyi` reader resolves an overload target against module procedures first. Targets are written in that same order, so the contract names the declaration its reader finds -- the other one routes a private procedure the bridge cannot reach. - Completion walked only top-level classes, leaving nested classes' members unnamed. Two class-naming assertions change from `PointType`/`point_t` to `Pointtype`/`Point_T`. Those tests emitted without completing policy, so the printer had printed the raw semantic name -- a name no build published; the runtime export was already `Pointtype`/`Point_T`, and the contract now agrees. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Export completion now records every declaration's decision, publishing nowhere included: a private declaration, one a contract leaves out of __all__, and a nested class all record []. completed_python_exports() fails on a declaration it never completed instead of publishing it under its own name, which is what bound a leaf contract's imported sibling type in the leaf. build_function_wrapper_policy() takes module_export explicitly; the type-bound-target guess is gone. The plan kept a type only where it was published, so existence and publication were one decision. One placement rule now defines each type: where it is published, under those names; beside its parent class when it is nested and unpublished; at the root, unbound, otherwise. It replaces the two copies of the export grouping in the derived-type and class planners. DerivedTypePlan carries contract_name and nested_in, and one type_definition_name() rule names the class generated code reaches, so the binding, Python surface, docstrings, and polymorphic variants read the plan instead of filtering surface.python_names. A nested class is bound on its parent (module.outer.inner). A cleanup action on a returned derived object read its family from the type's spelling, so a result written through an import alias failed planning. LifecyclePolicy now carries the transfer's derived handoff and the name-guess fallback is deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A generated contract copied its module's `use` statements and then the printer patched the list with five synthesizers, each deduplicating its own way, and spelled every item through three name maps and a normalization fallback. A facade extending a generic it `use`s from two modules therefore imported `convert` from each of them beside declaring the merged generic, and the package it generated could not be read back. complete_contract_imports() now completes each module's imports once, after names, over the modules written together. A contract binds the names its declarations use and the names it publishes, and every binding passes one table keyed by local name: a module never imports from itself or a declaration it carries, one entity reached twice binds once, and a name meaning two entities is refused. Each item records both spellings, and the printer renders them without choosing or spelling anything; its synthesizers, _public_import_name, _verbatim_import_names, the namespace validators, and the declared_prototype_names/contract_names_by_module plumbing are deleted. fortran2ir no longer mirrors `use` statements. It records the use associations the module's declarations depend on, each read from the module declaring the entity, sharing one resolution pass with re-export records. A re-export from a compiler-supplied module records the kind `intrinsic`, which publication policy treats as unpublishable, so neither the printer nor the planner keeps its own intrinsic-module check. A renamed import written with capitals now binds the name its annotation writes; it bound the normalized published spelling before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A type a module used in a signature and also published was spelled by two decisions: export naming gave the published name a class spelling for __all__, while the annotation, which no completion reached, wrote the use statement's spelling. One import can bind only one of them, so either the annotation or __all__ named something the contract never bound, and the generated package could not be read back (from .shapes import Point beside p: point). Naming completion now records one spelling for every name a module imports, under CONTRACT_IMPORT_NAMES_METADATA: a re-export's completed name, or, for a type the module does not re-export, a class spelling held in the same ledger. Type annotations, base classes, the import binding, and __all__ all read it. A re-export is spelled by its entity whether or not it is published -- a type as a class, a prototype as declared -- so the dependency-only branch and the printer's and planner's prototype special cases are gone. A name the module's own declarations use as a type is spelled as one even where its declaring module was not read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
…llee SemanticExpressionCallable.name is documented as the contract spelling, but it kept the source spelling while contract-import completion bound the callee under its completed name. A specification function named `lambda` was imported as `lambda_` and one colliding with it as `lambda__2`, while the shape still called lambda(n) -- not Python at all -- so such a function could size an array neither from source nor from its contract. The parser could not even read the call: a native name Python reserves failed the expression parse. The declaration-expression parser now sets a reserved native name aside through Python's tokenizer and restores it in the tree, so the call is read. Contract-name completion sets each callable reference's name to the name the contract binds the callee by, and respells that call in the public shape through the parsed expression, changing call targets only; native_name and native_scope keep reaching the Fortran function. Imported calls are spelled once, when the imported names are recorded, and calls to a declared callable by that callable's identity, so completing again reads the decision back. The planner binds each reference under the spelling it carries. A build's merged wrapper module now owns copies of its source modules. Completing it for the build mutated the very modules the build's contract package was written from, so the contract read spellings completed for a different namespace. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
The parser CLI converted each module alone and printed it without completion or import planning, a second .pyi route beside the contract pipeline. It now converts every inspected module together and emits them through emit_module_stubs(), so --pyi shows the contract `prik generate --pyi` writes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A declaration expression may call only a pure function, and policy checks the declaration it calls. A Fortran source records that purity, but a contract could state it only on a prototype -- @pure elsewhere was rejected and the printer never wrote it -- so any generated contract whose array extents call an imported function read that function back impure and failed to build with "must be pure", while the source it came from built. The loader now accepts @pure on a module-level function and records it the way Fortran source does, and the printer writes it for a module function whose Fortran attributes are pure. A method and an @overload dispatcher still reject it: neither names the native procedure being described. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
The binding checks every declared extent it can evaluate, but a specification function can only be evaluated in Fortran, so an axis the bridge sizes was skipped. A shorter intent(out) actual such as y(extent_for(n)) was then written past its end, corrupting the heap, and a shorter intent(in) one was read past it. Each argument with such an axis now has an argument_extent entrypoint group: the bridge evaluates the declared extent into it, runs the native procedure only when every actual matches, and the binding raises the same TypeError a binding-checked extent raises, with the cleanup a native status error uses. An omitted optional actual has no extent to check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
a2e4f7d guarded the native call alone. On a mismatch the bridge still converted its actuals, allocated the result, malloc'd result storage, and copied the never-written result into it; the binding then ran the derived-transaction checks and the array copy-back before rejecting. The bridge now evaluates each declared argument extent from its parameters before anything else and runs the rest of the procedure only when every actual matches, so a mismatch prepares, calls, and produces nothing and returns a null pointer result. The binding rejects as the first step after the call, releasing only what it took before the call. That release -- string buffers, array temporaries, and native result storage -- was spelled out in seven post-call failure paths; it is now one helper they all read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
The entrypoint plan recorded only that an argument or result had an extent group; the C binding and the Fortran bridge each re-derived which axes the bridge evaluates and what the output carrying each is called. NativeEntrypointParameterPlan now carries those extents -- axis and ABI name -- and both backends read them: the paired _bridge_extent_axes, _argument_extent_name, and _declaration_extent_result_* helpers are deleted, and one group kind set covers result and argument extents alike. The rejection boundary 1b26c74 drew gets the runtime guards the review asked for: an array crossing through a COPY_F temporary is left untouched by a rejected call, and a derived result a rejected call never produced surfaces the shape error rather than a missing-result error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
fortran2ir still ran the one NamingPolicy allocation left in semantic conversion: _settle_prototype_contract_names() gave each prototype a Python spelling and rewrote SemanticPrototype.name, the callback annotation's name, and its reference's local_name before export policy ran. It held every use-associated name while doing so -- the model contract imports have since replaced -- so a procedure-local interface was suffixed for a name the contract never binds. Conversion now keeps only identity: a prototype's module, declaring procedure, and native name, and a callback reference to it. Contract-name completion spells prototypes in the module's ledger -- a module's own before any withheld declaration, a procedure-local one after, qualified by its scope, both keeping their declared case -- and gives each callback annotation its prototype's spelling; the printer reads both. A callback's prototype policy was identified by module and name, so two procedures each declaring a `cb` stayed apart only through the spelling the converter had settled. Its identity now includes the declaring scope. The settlement, fortran2ir's NamingPolicy import, and its helper walk over a module's types are deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
ProcedureOverloadSet carries its own metadata, yet its Python export
decision was stored on its first specific, and four places encoded that:
_owner_metadata() in export policy, _declaration_metadata() in the build
pipeline, _entry_exports() in completion, and build_module_overload_policy
reading completed_python_exports(first). The generic was the conceptual
owner and an arbitrary specific the physical one, and a generic without
specifics had nowhere to keep a decision -- the adapter returned a
temporary {} that was discarded.
The decision now lives on the generic, every reader reads
declaration.metadata as it does for any other declaration, and the three
adapters and the empty-generic special case are deleted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
Policy stopped selecting a native-kind copy for logical arrays once a NumPy integer of each element's own width became their buffer: _array_logical_argument_abi() returns only NOT_APPLICABLE or C_BOOL_VIEW, with both copy flags false, and _array_writeback_abi() only NOT_APPLICABLE or NATIVE_ARRAY. Everything that lowered or validated the other answers stayed, reachable from no completed plan: - ArrayLogicalABI.NATIVE_KIND_COPY and its reason string; - the array_copy_in / array_copy_out policy and plan fields, their projection, and their consistency diagnostics; - the array writeback ABI -- enum, policy and plan field, producer, and validator -- whose only distinguishing value was never produced; - the bridge's logical-array copy initializers and finalizers, the exact-kind declarations, the low-bit writeback, and their helpers, plus _array_native_argument_expression(), which collapses into the boundary expression it wrapped. A test that rejected an edited plan restoring the low-bit writeback pinned behavior that no longer exists and is removed; the Boolean writeback test keeps its invariant that the bridge passes the array straight through. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A type's class, wrapper helper, and operation maps are defined in the one namespace that defines the type, but generated code looked them up in the namespace of the calling procedure. A procedure of another module returning, accepting, or calling back with the type raised AttributeError, and a generic or polymorphic argument of it never matched. The binding now retains the module object of each namespace defining a type and fetches the type's artifacts there; derived module variables, which already worked this way through per-variable owners, use the same retained namespace object. The callback context no longer carries a module. Those artifacts, and the class constructor entrypoint, were also named after the type's native spelling, so two modules declaring a type spelled alike could not be built together. They are keyed on the backend symbol, which is already qualified where native names collide. Docstrings find a published class name by type identity for the same reason. A plan defining one type in two namespaces is rejected before emission. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
A generated class looked its base up among the classes of its own namespace, so a type extending another module's type failed generation with a KeyError. It now names the base, and the base's operation map, through the namespace defining it, which module initialization binds into the setup script's dictionary before it runs. That namespace has to be set up first. Planning orders namespaces so each comes after the ones defining the bases its classes extend, keeping path order otherwise, and module initialization creates every namespace before setting any of them up. Validation already required this order; it held only when path order happened to agree. The derived-types guide also states that an allocatable derived function result must be allocated when the function returns. gfortran reads the result before any code the bridge can generate sees it, so an unallocated one cannot be turned into None. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.