ek9_extract_method
Inserts the new method (parameters and return inferred from variable data-flow) and replaces the block with a call to it; answers status, edits[] and summary, an ambiguous or boundary-crossing block as conflicts[].
When to use it
'Pull this block of a method, function or program into computeSubtotal' is one call with file, fromText and toText (the block's first and last line as written - no line counting) and name; parameters and the return are inferred, and copies of the block in the same body become calls too.
When not to use it
- Extracting an EXPRESSION into a local - use
ek9_extract_variable; a literal into a constants block - useek9_extract_constant. - Extracting MEMBERS into a new trait rather than a code block - use
ek9_extract_trait. - Writing a fresh method rather than lifting existing code - use
ek9_add_method. - Turning a loop body into a stream pipeline - use
ek9_advise_loop.
Details
Parameters = outer-scope vars read/written in the block; return = a var produced in the block and read after (or the enclosing return var written in the block); 'as pure' whenever the new member compiles pure - the compiler's purity rules decide (printing through Stdout is pure in EK9; mutation and non-pure calls are not) - and always from a pure source member. Conflict kinds (all mechanical): 'return-type-ambiguous' (≥2 outflows, supply multiReturn=formal-record|formal-class|dynamic-class, AI-chosen from downstream intent), 'block-spans-control-flow' (multi-exit selection - narrow to one scope), 'variable-flows-in-and-out' (in-out accumulator - EK9 forbids param reassignment; narrow so the var is only-consumed or only-produced), 'extracted-block-references-outer-state'. V1 scope: member methods + top-level functions; containerName defaults to <Name>Result. A selection ending in the enclosing member's return line returns that member's whole result - the summary says so with the range without it; returnType:'Integer' takes that range when it returns the type asked for (corrected[]), else refuses (return-type-not-produced).
Arguments
| Argument | Type | Required | What it is |
|---|---|---|---|
containerName | string | Optional name for the synthesized container type (PascalCase). Defaults to <Name>Result. | |
endLine | integer | Or the last line of the block (inclusive, 1-based). Same body as startLine. | |
file | string | yes | The .ek9 source file (workspace-relative; an absolute path inside the workspace is accepted). |
fromText | string | The block's FIRST line exactly as written (indentation ignored) - instead of counting startLine. Must name one line; add more of it when it occurs twice. | |
multiReturn | string: formal-record | formal-class | dynamic-class | When ≥2 values flow out of the block, the container shape that packages them into ONE return: 'formal-record' (top-level record, public fields, call site reads bundle.field), 'formal-class' (top-level class with accessor methods, bundle.field()), or 'dynamic-class' (inline captured class, no separate construct). Omit for a single value out; ≥2 out without it → return-type-ambiguous conflict listing these options. AI-directed: pick the shape from downstream intent (incidental bundle vs data carrier vs domain type). | |
name | string | yes | Name for the new method (camelCase). |
replaceDuplicates | boolean | Default true: every other copy of the selected statements in the same body that is PROVEN the same computation (same shape, names mapped one-to-one, same types, only the same value flowing out) is replaced with the call too; near misses are listed in duplicates[] with the reason, never replaced. | |
returnType | string | Optional: the type the new member returns ('Integer'). A selection ending in the enclosing member's 'rtn: ...' line returns that member's whole result; when that is not the type asked for, the one other reading - those trailing return lines stay where they are - is taken if it returns the type asked for (said in corrected[]), else refused naming what each reading returns. | |
startLine | integer | Or the first line of the block (inclusive, 1-based) - an IDE selection. Inside a method, function or program body. | |
toText | string | The block's LAST line as written - the first line matching it at or after fromText. Omit it for a one-line block. |
It also takes the arguments every tool takes.
Recorded calls
Real calls, recorded against small projects and checked on every build - a changed answer fails it. 3 of the 12 recorded are shown.
Misaligned selection names whole statements
Request
{
"endLine" : 19,
"file" : "task.ek9",
"name" : "total",
"startLine" : 16
}
Response
{
"conflicts" : [ {
"kind" : "block-spans-control-flow",
"message" : "The selection 16-19 does not align with whole statements - it starts above the body, whose statements begin on line 18. The statements it touches are lines 18-20: re-issue with startLine:18, endLine:20 to extract them.",
"suggestedRange" : "18-20"
} ],
"edits" : [ ],
"meta" : {
"serverMs" : "<timing>"
},
"risk" : {
"crossesModuleBoundary" : false,
"estimatedSeverity" : "trivial",
"fileCount" : 0,
"lineCount" : 0,
"touchesPublicApi" : false,
"touchesTests" : false
},
"status" : "conflicts",
"summary" : "Selection crosses a statement / control-flow boundary",
"workspaceHash" : "<workspace-hash>",
"wouldBeDiagnostics" : [ ],
"written" : false
}
Selection from blank line before method
Request
{
"endLine" : 18,
"file" : "task.ek9",
"name" : "total",
"startLine" : 15
}
Response
{
"conflicts" : [ {
"kind" : "block-spans-control-flow",
"message" : "The selection 15-18 does not align with whole statements - it starts above the body, whose statements begin on line 18. The statements it touches are lines 18-18: re-issue with startLine:18, endLine:18 to extract them.",
"suggestedRange" : "18-18"
} ],
"edits" : [ ],
"meta" : {
"serverMs" : "<timing>"
},
"risk" : {
"crossesModuleBoundary" : false,
"estimatedSeverity" : "trivial",
"fileCount" : 0,
"lineCount" : 0,
"touchesPublicApi" : false,
"touchesTests" : false
},
"status" : "conflicts",
"summary" : "Selection crosses a statement / control-flow boundary",
"workspaceHash" : "<workspace-hash>",
"wouldBeDiagnostics" : [ ],
"written" : false
}
Block named by text
Request
{
"file" : "task.ek9",
"fromText" : "total <- 0.0",
"name" : "sumOfLines",
"toText" : "total += line"
}
Response
{
"conflicts" : [ ],
"edits" : [ {
"after" : " total <- sumOfLines()",
"before" : " total <- 0.0\n for line in lines\n total += line",
"endLine" : 20,
"file" : "task.ek9",
"startLine" : 18,
"type" : "replace"
}, {
"after" : "\n sumOfLines()\n <- rtn as Float?\n total <- 0.0\n for line in lines\n total += line\n rtn := total",
"endLine" : 23,
"file" : "task.ek9",
"startLine" : 23,
"type" : "insert"
} ],
"meta" : {
"serverMs" : "<timing>"
},
"risk" : {
"crossesModuleBoundary" : false,
"estimatedSeverity" : "trivial",
"fileCount" : 1,
"lineCount" : 4,
...
Cut short here: 9 more lines. The whole recorded call.
See also
- Show me in the IDE: Extract Method