Skip to content

Source Transformations

kimjooyoon edited this page Oct 2, 2026 · 7 revisions

Source transformations: change structure without inventing permission

2026-10-03 안내: 아래 예제와 관측은 각 절에 고정된 소스·도구체인을 기준으로 읽습니다. 최신 바디 생성과 자체 소형 모델의 흐름은 모델과 메타프로그래밍, 지금 사용할 버전은 현재 상태와 시작하기에 정리했습니다.

Gooo aims to connect declared intent, a semantic model, generated code, and evidence about what that code does. One practical consequence is that a proposed source transformation must explain what it preserves, not merely produce shorter code.

This page explains that boundary. It is not a claim that Gooo can safely refactor every Go program.

Structural refactoring and bug repair have different goals. A structural candidate preserves the selected observations; a repair intentionally changes a specified incorrect observation while retaining its other obligations. Generate an error-handling repair with Gooo walks through the latter with an actual source fixture, an executable Gooo input, generated Go, and separately attributed CI evidence.

Start with a small example

Suppose a function collects observations and then publishes them:

values := map[string]any{
    "first": observe("first"),
    "second": observe("second"),
}
publish(values)

There are two different jobs here: obtaining the observations and assembling a map.

Moving the whole block into another function can change the caller seen by observe. Publishing may also perform output or other effects. A compiler must not assume these calls are pure just because extracting the block would make a size indicator pass.

A narrower candidate keeps those calls in the original function:

values := collect(observe("first"), observe("second"))
publish(values)

The helper only constructs the data:

func collect(first, second any) map[string]any {
    return map[string]any{
        "first": first,
        "second": second,
    }
}

These are illustrative Go projections, not new Gooo syntax or a public refactoring command. The argument expressions still execute in the original caller before the helper is entered. The helper does not obtain observations or publish anything.

What makes this a Gooo operation?

The repository's ExtractFunction operation is bound to the embedded Gooo operation-input contract. Its implementation consumes the contract's source digest, semantic digest, input/output entities, and provenance relations.

The same six obligations accompany the transformation:

Obligation The question it answers
Return shape Are the relevant result types and returns preserved?
Control flow Did a return, branch, deferred action, or concurrent action move?
Free bindings Does each value still refer to the correct variable in the correct scope?
Callee effects Which calls move, and what justifies moving them?
Rendered capacity Do the actual generated units fit the existing limit?
Projected conformance Does the resulting package type-check against its dependencies?

The implementation bridge is operation-input contract evidence. A nonempty digest is an identity binding, not a signature or a proof that the transformation is useful.

For the narrow map candidate, the callee-effects argument is no original call moves into the constructor. It is not a whitelist that declares unknown functions safe.

Why can an extraction stop?

Three situations deserve different explanations:

Situation What the user should learn
An eligible candidate has evidence Which candidate was generated, which obligations were checked, and where to inspect its output
A candidate violates a known boundary What concrete constraint it violates; this does not mean the original program is invalid
Evidence is missing Where the proof stopped, what information is missing, and the next operation that could resolve it

For example, CALLEE_EFFECTS_UNPROVEN means the extractor could not establish the effects of a call it proposed to move. It does not mean the call is malicious, incorrect, or necessarily impossible to extract.

An alternative strategy may avoid moving that call. If so, retain the first strategy's failure alongside the successful alternative. Do not rewrite the historical failure into a success.

A failed strategy is not a failed program

A compiler diagnostic should tell you what the compiler tried to do. A restriction on one transformation is not automatically a restriction on the input language.

Consider this illustrative Go projection:

command := factory()
value, err := command.Run(argument())
if err != nil {
    panic(err)
}

A strategy that moves execution into a helper may reject the panic because its control-flow proof does not cover that move. That is a valid rejection of that strategy. It does not establish that the original program is wrong.

A different, caller-local candidate might combine only the first two statements:

value, err := factory().Run(argument())
if err != nil {
    panic(err)
}

The factory, argument evaluation, method invocation and panic still belong to the original function. This is not a general license to inline variables: the bounded compiler rule requires an adjacent, single-use, type-bound pointer receiver and rejects intervening effects, overlapping comments and effectful assignment targets. Its original/generated runtime comparison must still run.

The important distinction is the scope of the missing or negative evidence:

What stopped? What may happen next?
A particular strategy cannot preserve its required control-flow boundary Reject that strategy; a different candidate must satisfy its own obligations under the same goal
The operation contract or required type evidence is missing Restore that evidence; changing strategies does not supply it
The reason is unknown or merely resembles a familiar error message Keep it unresolved; do not infer permission from wording
The candidate violates a shared acceptance criterion Reject the candidate; do not lower the criterion to make it pass

These boundaries were merged into dev in PR #844. Native unit, race, semantic and policy checks passed at the accepted head. This is development-branch acceptance, not a published release or support for every caller-sensitive program. The acceptance record links the exact source, CI run and proof artifact; it does not replace the separate next-use result.

Keep the unsuccessful attempt in the explanation

The successful explanation, when one exists, must not erase the earlier attempt:

same input + operation contract + acceptance criteria
    -> attempt A: execution relocation
       rejected at control-flow admission
    -> attempt B: caller-local preparation
       independently checked, accepted or rejected
    -> actual generated output and later consumer execution

If attempt A stopped at control-flow admission, its record must not claim that callee-effect analysis ran and failed. Keep the actual stage, obligation, reason and original diagnostic. Record attempt B separately. An alternative candidate does not turn attempt A's rejection into a pass.

This is why Gooo connects operations to provenance rather than treating the final generated text as the whole result. A reader needs to recover both the artifact and the reasoning boundary that selected it, without reconstructing that boundary from a conversation.

See Frozen Goal Selection for keeping the objective fixed, and Reading Syntax Failures for reading a source-bound counterexample.

UNKNOWN should tell you where to resume

An unresolved result needs all six fields:

Field Meaning
stage The broad phase that could not complete
step The particular operation within that phase
reason A stable, searchable explanation code
unknown_class Whether evidence is missing, blocked, stale, ambiguous, or unbounded
next_operation The explicit next action, not an inferred suggestion
blocked_by The known dependency frontier; an empty array is meaningful

Do not substitute a green process exit, a hash match, or a smaller line count for the missing semantic evidence.

A size limit is not the purpose

A function's source body may fit while its standalone generated unit does not. Imports, documentation, and the rendered helper belong to the actual measurement.

The extractor measures the existing 75-line rendered limit. During preparation, total overage must strictly decrease. Final generated units must close the remaining overage. An intermediate decrease is not a completed transformation.

The useful outcome is an independently checkable generated program. The size observation helps select and bound a candidate; it is not a language-quality score.

What must be compared at runtime?

Type checking alone does not prove runtime behavior. A bounded comparison can inspect exact observations such as:

  • The order and number of value-producing calls.
  • The caller identity observed by those calls.
  • Whether output happens before or after those calls.
  • Whether nil and typed nil remain distinguishable.
  • Whether panic occurs at the same point and prevents later output.

Original and generated programs should be compared against an explicit expected result, not only against one another. Two equally wrong outputs are not an acceptance argument.

See Testing and Reuse for the distinction between declared tests, actual executions, and reusable evidence.

Current implementation boundary

As of 2026-09-14, the caller-preserving transformation is available in dev at 0040869f1bb1c09930647a7c9ef5fea006a81dbe, through PR #844. This is development-branch integration, not a release or a claim that every motivating workload is solved.

The compiler can keep map-value calls in the original function while extracting map construction. When the bounded rule applies, it can also fuse an adjacent, single-use pointer receiver binding without relocating the factory, arguments or method call into the helper. A rejected strategy remains in the explanation even when another strategy is selected.

The accepted head passed its native checks and produced a source-bound CI proof with PASS and BOUND provenance. The native run and proof artifact record this acceptance. The six extraction obligations and final 75-line rendered limit were not lowered.

Supported shape, not a general inlining promise

Map candidates remain limited to direct short declarations of map[string]any, distinct literal string keys, and no overlapping comments or package-level shadowing of emitted built-in type names. Receiver fusion additionally needs the adjacency, single-use and type evidence described above. Every candidate must make measured capacity progress and pass projected conformance.

This does not promise arbitrary map extraction, a chosen order for otherwise unspecified operand reads, identical allocation timing, lower memory use, or lower runtime. Performance improvement remains unknown without a comparable before/after pair.

Follow the original workload, not just the new feature

The motivating consumer is PR #841. Its original source-bound failure recorded a rendered unit of 84 lines against the 75-line limit. Its admission path had separate native evidence; that other path passing did not resolve the extraction failure.

The predecessor compiler, PR #842, was then consumed by that unchanged workload at 5fc111601fc5e75d462f10c9860c85ef542afc9c. The native projection result records an intermediate function of 63 lines and a rendered helper of 76 lines, still above the unchanged limit. One unhandled input remained, with UNKNOWN / CALLEE_EFFECTS_UNPROVEN, and final extraction was not applied. This predecessor result is retained, not rewritten into success.

The original workload consumed the accepted #844 compiler at fd9f8dd8e49fdc80ec3178723e339e679ff5a07a. Its input function was not hand-edited to make the limit pass. The native repository projection now records final extraction of that original subject, not merely a promising candidate.

Observation within that native run Before preparation Final candidate
Rendered outer helper physical lines 84 75
Overage against the unchanged 75-line limit 9 0
Function physical lines 71 62

The separate map-constructor helper has 19 physical lines. Final capacity reports zero overage across five generated units. All six existing extraction obligations report PASS. The subject is COMMITTED_APPLIED, and its replacement receipt records a successful write with matching temporary and final digests. Here, "committed" describes the extractor applying its prepared change inside the CI workspace; it does not mean the consumer PR was merged.

This is a small, concrete compiler-development loop: an actual workload exposed a limitation, a bounded compiler change was accepted, and the unchanged workload used that accepted compiler on its next run. It is not a claim that the compiler autonomously invented its own implementation.

Read the evidence at the right level

The extraction report binds this result to func:verifyUpstream, source digest sha256:0711778497718434f4330586b92e7e4bb406cf0bc2668e7a6584b3cbacaa5426, and the Gooo operation-input source and semantic digests. Its six proof stages retain the source, candidate and linked evidence identities. The 84-to-75 comparison is from this one run; the earlier unsuccessful 76-line result remains a separate historical observation.

To inspect it, open repository-projection/extraction-report.json in the linked artifact and locate scripts/self-improvement-public-partial-reuse/upstream.go. Read its strategy_evidence, proof_stages, final_rendered_capacity and replacement receipt together. The artifact archive SHA-256 is 622acf688a7ceb82f25a2df375d81034d7bad07b5c526fe249674a677b0b9790.

Keep three boundaries visible:

  • Projected conformance reports package type-checking and expressly does not assert runtime conformance. The extraction receipt alone is not a proof of every generated program behavior.
  • Applying this extraction is not the same as a whole-repository transition. The separate repository-projection/cutover-evidence.json still reports applied=false.
  • Passing a compiler obligation does not grant permission to change CI policy, merge a protected change, or publish a release.

This consumer also changes protected CI wiring. Its last observed permission receipt is FAIL_CLOSED because the authorized candidate binding is not exact. As checked on 2026-09-14, PR #841 is closed and unmerged at fd9f8dd8e49fdc80ec3178723e339e679ff5a07a; it is a deferred admission case, not an active or accepted implementation PR. The successful extraction evidence remains useful, but closing the PR did not close that permission obligation. This boundary does not decide whether the compiler can transform the input, and a successful transformation would not authorize the CI change.

Development is provenance too

The development record should make this path inspectable:

observed consumer failure
    -> bounded transformation candidate
    -> original/generated comparison in CI
    -> accept or reject under unchanged criteria
    -> accepted compiler consumed by the original workload

Candidate generation, CI acceptance, development-branch integration, release, and later use are distinct events. Each should keep its own source identity and outcome.

The loop is not closed merely because a new strategy has a name, a PR exists, or a report says that it is promising. The original workload must actually consume the accepted result on its next execution.

Gooo

배우기

직접 다뤄 보기

원리와 개발

공개 코드와 모델

Clone this wiki locally