-
Notifications
You must be signed in to change notification settings - Fork 86
Closed
Milestone
Description
This issue serves as a detailed spec for the refactoring of the representation of values for #193
Design of value objects
Value objects are immutable. Their API is modelled after the dart-sass classes for values, with a few differences:
- we don't distinguish between an internal implementation and an external type. This is a "hack" used in dart-sass to workaround the absence of compiler-enforced internal APIs in dart (which will be solved in the next version of dart AFAIK so dart-sass will probably change that in the future anyway). In the PHP ecosystem, IDEs are warning when using methods tagged as
@internal. Psalm also reports that as an error during static analysis (for phpstan, there is a feature request). So using@internalwill be considered good enough - some methods will have slightly different names due to PHP reserved keywords being forbidden in method names on PHP 5.6 (for instance
SassList.empty()from dart will becomeSassList::createEmpty()in PHP) - overriding the
==operator is not possible, so we'll implement aequals(Value $other): boolmethod that should be used. that method should be used in any code wanting to compare 2 Sass values - methods returning a
List<Value>will return a PHP array (which solves easily the fact that dart enforces that returned collections are not modifiable, as PHP array are not passed by reference) - for
Map<Value, something>, we'll need to find a replacement API.SplObjectStorageis not suited for that as it usesspl_object_hashfor equality by default, and its extension pointSplObjectStorage::getHashwould be very hard to use to implement equality rules (In other languages, like dart for instance, classes have ahashcodeAPI returning an integer. But then, for such equality checks, objects returning the same hashcode are still compared with==. So the implementation ofhashcodecan return false positives but not false negatives, which makes things a lot simpler)
Implementation plan
The goal of this plan is to perform an incremental migration of the codebase. This is probably not meant to be released until the end of the migration though, to avoid exposing the migration layer in releases (and then having to keep it for backward compatibility). But that will allow to keep a working 2.0-dev compiler during the migration (and spreading the migration across separate commits/PRs to make it easier).
- Implement
Valueand its subclasses - Add support for
Valuein the compiler (not reading$value[0]on them, making them no-op inreduceand maybe a few other stuff) Add support forcurrently, this is handled inValuein the formatter (being able to output them)Compiler::compileValueactually, the formatter does not deal with our array-based value API but a separate API.- Implement conversion methods in the Compiler to switch between
Valueand the legacy array-based representation - Implement a way to opt-in for the new API for functions (I'm thinking about a private static variable holding an array of
function name => bool, and maybe a way for external functions to opt-in if needed unless we keep that for the end if we never release the migration layer).callNativeFunctionwill convert the arguments and the return value based on this opt-in. Some rules apply to functions when using the new API:- functions must define their prototype (in 1.x, the prototype is optional, and the structure of arguments is different depending on that...)
- the signature of the callable is
function(array<Value> $args): Value - when using a rest argument (must be the last one in the prototype), the argument is received as a
SassArgumentList(in the position of the rest argument in the prototype), allowing to access both positional and keywords argument - when using a rest argument, an error is thrown if the function is called with keyword arguments but the keyword args are never accessed in the
SassArgumentList(same than for dart-sass)
- Migrate functions to the new API one by one (or more likely by batches of related functions)
- Migrate the environment to store variables as values (at that point, the implementation of
setVariableschanges too) - Migrate the evaluation of expressions to use the new API (can be done in parallel of the migration of functions, by changing the canonical representation in
callNativeFunction) - Migrate the parser to parse literal values as
Valueobjects - Remove the conversion layer as the array-based API is used only for AST nodes at that point, not for values
Metadata
Metadata
Assignees
Labels
No labels