Functions are not inherited by amending modules #103

Closed
opened 2025-12-30 01:20:50 +01:00 by adam · 4 comments
Owner

Originally created by @philippemerle on GitHub (Mar 4, 2024).

The language reference says that a module amending another module inherits from all the members of the amended module. But functions of the amended module seem to be not inherited from as shown in the following example.

File base_template.pkl

persons: Listing<Person>
class Person {
  name: String
}
function buildPerson(val: String): Person = new {
  name = val
}

File my_template.pkl

amends "base_template.pkl"

persons {
  new Person { name = "Me"}
  buildPerson("you")
}

Evaluating my_template.pkl produces the following error

–– Pkl Error ––
Cannot find method `buildPerson`.

5 | buildPerson("you")
    ^^^^^^^^^^^^^^^^^^
at my_template#persons[#2] (file:///.../my_template.pkl, line 5)

3 | persons {
    ^^^^^^^^^
at my_template#persons (file:///.../my_template.pkl, line 3)

106 | text = renderer.renderDocument(value)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/0.25.2/stdlib/base.pkl#L106)

Do I miss something?

Originally created by @philippemerle on GitHub (Mar 4, 2024). The language reference says that a module amending another module inherits from all the members of the amended module. But functions of the amended module seem to be not inherited from as shown in the following example. File `base_template.pkl` ``` persons: Listing<Person> class Person { name: String } function buildPerson(val: String): Person = new { name = val } ``` File `my_template.pkl` ``` amends "base_template.pkl" persons { new Person { name = "Me"} buildPerson("you") } ``` Evaluating `my_template.pkl` produces the following error ``` –– Pkl Error –– Cannot find method `buildPerson`. 5 | buildPerson("you") ^^^^^^^^^^^^^^^^^^ at my_template#persons[#2] (file:///.../my_template.pkl, line 5) 3 | persons { ^^^^^^^^^ at my_template#persons (file:///.../my_template.pkl, line 3) 106 | text = renderer.renderDocument(value) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ at pkl.base#Module.output.text (https://github.com/apple/pkl/blob/0.25.2/stdlib/base.pkl#L106) ``` Do I miss something?
adam closed this issue 2025-12-30 01:20:51 +01:00
Author
Owner

@stackoverflow commented on GitHub (Mar 4, 2024):

Scoping rules in Pkl can be a bit tricky. You can use module to reference top level variables and functions:

amends "base_template.pkl"

persons {
  new Person { name = "Me"}
  module.buildPerson("you")
}

Also: if you use our IntelliJ plugin you should see an error telling you this function is not found.

@stackoverflow commented on GitHub (Mar 4, 2024): Scoping rules in Pkl can be a bit tricky. You can use `module` to reference top level variables and functions: ``` amends "base_template.pkl" persons { new Person { name = "Me"} module.buildPerson("you") } ``` Also: if you use our IntelliJ plugin you should see an error telling you this function is not found.
Author
Owner

@philippemerle commented on GitHub (Mar 4, 2024):

Thank you the provided solution. But this will be simpler if inherited functions could be called directly without prefixing them by module.. Perhaps in a future pkl release?

@philippemerle commented on GitHub (Mar 4, 2024): Thank you the provided solution. But this will be simpler if inherited functions could be called directly without prefixing them by `module.`. Perhaps in a future pkl release?
Author
Owner

@bioball commented on GitHub (Mar 4, 2024):

The scoping rules in place here provide an important safety net; that the resolved member can't change by adding something else in the (grand)parent module.

Without this rule, it would be possible to change what a variable/method resolved to by changing something in the base module. It would make it so that adding names to a module might produce very unexpected results in the generated configuration.

For example, if this worked:

parent.pkl

function foo() = "bar"

class MyObject {
  prop: String
}

obj: MyObject

child.pkl

obj {
  prop = foo()
}

Then, a new function foo() was introduced:

 function foo() = "bar"

 class MyObject {
   prop: String
+
+  function foo() = "baz"
 }
 
 obj: MyObject

This change seems innocent when just looking at parent.pkl, but it would change foo() within child.pkl to resolve to a different method. This behavior would make it too easy for pkl code to break in hidden ways.

The same resolution rules apply to properties, too.

@bioball commented on GitHub (Mar 4, 2024): The scoping rules in place here provide an important safety net; that the resolved member can't change by adding something else in the (grand)parent module. Without this rule, it would be possible to change what a variable/method resolved to by changing something in the base module. It would make it so that adding names to a module might produce very unexpected results in the generated configuration. For example, if this worked: parent.pkl ```groovy function foo() = "bar" class MyObject { prop: String } obj: MyObject ``` child.pkl ```groovy obj { prop = foo() } ``` Then, a new `function foo()` was introduced: ```diff function foo() = "bar" class MyObject { prop: String + + function foo() = "baz" } obj: MyObject ``` This change seems innocent when just looking at `parent.pkl`, but it would change `foo()` within `child.pkl` to resolve to a _different_ method. This behavior would make it too easy for pkl code to break in hidden ways. The same resolution rules apply to properties, too.
Author
Owner

@bioball commented on GitHub (Mar 4, 2024):

Closing as not planned, as this works as-intended.

@bioball commented on GitHub (Mar 4, 2024): Closing as not planned, as this works as-intended.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/pkl#103