Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple use of unlocking mixin within the same scope #1291

Closed
SomMeri opened this issue Apr 23, 2013 · 9 comments
Closed

Multiple use of unlocking mixin within the same scope #1291

SomMeri opened this issue Apr 23, 2013 · 9 comments

Comments

@SomMeri
Copy link
Member

SomMeri commented Apr 23, 2013

This is about "unlocking mixins" feature #73. I'm not sure whether this is a feature or a bug, but I'm guessing a bug.

If the same mixin is called twice, only mixins from the second call are imported. First import mixins are ignored.

This:

.bananas() {
  height: 11px;
}
.apples(@width) {
  .bananas() {
    width: @width;
  }
}

.apples(55px); // this one does not return any mixins
.apples(99px);

#test {
  .bananas();
}

Actual result:

#test {
  height: 11px;
  width: 99px;
}

Expected result:

#test {
  height: 11px;
  width: 55px;
  width: 99px;
}

It does not seem to be terribly important, I mostly need to know whether it works as intended or not.

@jonschlinkert
Copy link
Contributor

If I recall correctly, it was requested that Less mixins not produce duplicate properties, so I think this was intentional, e.g. a feature. I think properties are merged with the last properties "winning". @lukeapage am I correct?

@SomMeri
Copy link
Member Author

SomMeri commented Apr 23, 2013

I tried to simplify the example in description as much as possible, but cause slightly more complicated case uses different properties:

.tricky(@width) when (@width < 100) {
  content: do-something;
}

.tricky(@width) when (@width >=100) {
  declaration: do-something-else;
}


.apples(@width: 100px) {
  .bananas() {
    .tricky(@width);
  }
}

.apples(50px);
.apples(700px);

#test {
  .bananas();
}

produces:

#test {
  declaration: do-something-else;
}

but should produce (I think):

#test {
  content: do-something;
  declaration: do-something-else;
}

@seven-phases-max
Copy link
Member

Btw., (what we've learned in #1678): in examples above the first .apples() call does unlock .bananas(), the second call unlocks another .bananas() too but it also overwrites any values set by previous.apples() call(s), i.e. any .bananas() unlocked by .apples() will have the same variable/property/mixin values as set by the last .apples() call in any scope. (Hence it looks like there's only one .bananas() mixin being unlocked).

Example (adopted from #1678):

.apples(@value) {
    .bananas() {
        value: @value;
    }
}

.apples(1);

a {
    .apples(2);
}

b {
    .bananas();
}

result:

b {
  value: 2;
}

@SomMeri
Copy link
Member Author

SomMeri commented Dec 26, 2013

I think that parameter/variable value should be lock at the time mixin was "unlocked". Variables are usually solved from local scope including parent scopes. Search only then continues to caller.

If we would take your explanation, then this:

.apples(@value) {
    .bananas() {
        value: @value;
    }
}

.apples(1);

b {
    .bananas();
    @value: 2;
}

would have to compile into this:

b {
  value: 2;
}

However, it compiles into this:

b {
  value: 1;
}

Mixins unlocking would be less useful then. At least I think, I do not know how many people are using that.

@SomMeri
Copy link
Member Author

SomMeri commented Dec 26, 2013

Mixins that are not unlocked work the same way. Variables are solved first and declarations are copied only after that:

.mixin(@value) {
  property: @value;
}
pre:focus {
  .mixin(1);
  .mixin(2);
}

compiles into:

pre:focus {
  property: 1;
  property: 2;
}

and this

.mixin2() {
  @value:2;
  property: @value;
}
.mixin3() {
  @value:3;
  property: @value;
}

h1 {
  .mixin2();
  .mixin3();
}

compiles into:

h1 {
  property: 2;
  property: 3;
}

@seven-phases-max
Copy link
Member

If we would take your explanation

Sorry, probably my explanation was not clear enough. I only unfolded what happens there, I did not mean that this is expected behaviour. Notice how in my example the call to the second .apples(2); in the a scope changes the state of the .bananas(); unlocked in the global scope (while it should not be affected at all by normal scope rules). The same happens in your initial example:

.bananas() {
  height: 11px;
}
.apples(@width) {
  .bananas() {
    width: @width;
  }
}

.apples(55px); 
// ^ unlocks bananas with @width = 55px

.apples(99px); 
// ^ unlocks another bananas with @width = 99px
// *and* (this part is the root of the issue ->) also sets the @width value in previously 
// unlocked bananas to 99px (the value of 55px is simply wiped here)

#test {
  .bananas(); 
// ^ expands *two* banana mixins as it should but since the @width
// value in both is 99px we see only one width: 99px in the result
}

In other words the issue seems to be not in "first mixin is not unlocked" but in "last unlocking overwrittes internal state of all previously unlocked mixins".

@SomMeri
Copy link
Member Author

SomMeri commented Dec 26, 2013

@seven-phases-max Sorry back, I misunderstood you.

@seven-phases-max
Copy link
Member

This issue seems to be fixed in the current master (i.e. pre-1.7.0)... Cool, now my OOP example works as expected:

// class (in OOP sense):
.Person(@gender) {
    .sayGender() {
        output: @gender;
    }
}

// objects (instances of the class):
.person1 {.Person('Male')}
.person2 {.Person('Female')}

// usage
a {
    .person2.sayGender();
}

b {
    .person1.sayGender();
}

result:

a {
  output: 'Female';
}
b {
  output: 'Male'; // it was 'Female' before 1.7.0
}

Less: It is C++, only better!

👯

@seven-phases-max
Copy link
Member

Closing as fixed in 1.7.0.

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

No branches or pull requests

3 participants