Skip to content

Conversation

stackoverflow
Copy link
Contributor

@stackoverflow stackoverflow commented Aug 20, 2025

In some very specific scenarios cyclic dependencies may lead to a null pointer due to the module not being properly initialized.
Fixes #1183.

Copy link
Member

@bioball bioball left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is the correct fix for this bug.

Pkl allows circular imports (except in cases where a module's type is defined in terms of itself, like two modules extending each other).

Also, amending/extending modules expose types defined on their parent.

I created a reproducer for this bug in this issue here: #1183

I think either:

  1. We throw a meaningful error about some circular dependency
  2. We fix some deeper underlying issue so that this code works

BTW: can we add the reproducer as a test case here? You can add them to the input-helpers dir.

Copy link
Member

@bioball bioball left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start, but this needs to be refined some. See comments


var importedModule = (VmTyped) result;
if (importedModule.isNotInitialized()
&& importedModule.isModuleObject()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should always be a module object, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Removed the check.

}
}

module.setCachedValue(importName, result);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be cacheing this under the same name as the import value. Otherwise, this snippet breaks:

import "amendingFoo.pkl"

// here, `amendingFoo` should resolve to the prototype
res: amendingFoo.Foo

// here, `amendingFoo` should resolve to the import itself
bar = amendingFoo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. cycles2.pkl tests that (fails if you cache).

var amendedModuleKey = moduleInfo.getAmendedModuleKey();

if (amendedModuleKey != null) {
return VmLanguage.get(null).loadModule(amendedModuleKey);
Copy link
Member

@bioball bioball Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The amended module key itself may be amending another module; we need to keep going until we find the first non-amending module. Can we add a test for this?

Also, pass the node in when looking up the reference

Suggested change
return VmLanguage.get(null).loadModule(amendedModuleKey);
return VmLanguage.get(this).loadModule(amendedModuleKey, this);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed! I changed the test to have another layer of indirection.

if (prototypeModule != null) {
result = prototypeModule;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The result of getImport should always be the prototype in all cases, even when the module has not yet been initialized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

NullPointerException on circular import
3 participants