The Wayback Machine - https://web.archive.org/web/20201229011113/https://github.com/google/blueprint/pull/145
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

Remove pos #145

Open
wants to merge 4 commits into
base: master
from
Open

Remove pos #145

wants to merge 4 commits into from

Conversation

@mathjeff
Copy link
Contributor

@mathjeff mathjeff commented Mar 9, 2017

No description provided.

@mathjeff mathjeff mentioned this pull request Mar 9, 2017
@mathjeff
Copy link
Contributor Author

@mathjeff mathjeff commented Mar 9, 2017

@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch 2 times, most recently from b94f665 to e64127a Mar 10, 2017
@@ -639,23 +639,276 @@ func TestParseValidInput(t *testing.T) {
t.Errorf("test case: %s", testCase.input)
t.Errorf("length mismatch, expected %d definitions, got %d",
len(testCase.defs), len(file.Defs))

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

I suggest moving this to compare.go, and it seems worth including some tests for it on its own. Code-wise, it is very similar to clone and extend in the blueprint/proptools package, so maybe move it there, and try to make them structured similarly?

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

good idea

return found
}

// checks if two objects are the same class, returns True if they match

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

Go doesn't have objects or classes.
s/class/type/
s/object/value/

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

ok

if actual.item.Kind() != expected.item.Kind() {
return false, fmt.Sprintf("%s is of type %s whereas %s is of type %s", actual.name, actual.item.Kind(), expected.name, expected.item.Kind())
}
switch actual.item.Kind() {

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

This conversion is unnecessary, use
reflect.DeepEqual(actual.item.Interface(), expected.item.Interface())

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

cool

This comment has been minimized.

@mathjeff

mathjeff Mar 21, 2017
Author Contributor

Well I investigated a little further but it seems that unfortunately, x.Interface() fails if there were any private fields traversed while accessing x. My thought now is that it's preferable to have support for diffs of private fields if it requires reimplementing this method like this.

aKeys := a.MapKeys()
bKeys := b.MapKeys()
for _, aKey := range aKeys {
if !(isIn(aKey, bKeys)) {

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

This is unnecessary, use:
if !b.MapIndex(aKey).IsValid()

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

See below about keys being pointers

This comment has been minimized.

@colincross

colincross Mar 17, 2017
Contributor

What happens if two deeply equal pointers are keys in the same map? Only the pointer value will be hashed, so they will still be two different keys.

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

Yeah exactly; the test doesn't confirm that the pointers themselves are correct, only that they have a corresponding value in the other map.

Do you mean that we want to fix deepCompare for the following case?

////
var a and b, deeply equal to each other
var c and d, deeply equal to each other

map1{a:true, b:true, c:true}
map2{a:true, c:true, d:true}

?

for which deepCompare would return true when really we want false

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

I guess it could also be worth fixing this case too:

////
var a and b, deeply equal to each other
map1{a:true, b:false}
map2{a:true, b:false}

for which we want an answer of true but theoretically get a random answer (though I expect in practice it's always false).

Is that what you're referring to?

This comment has been minimized.

@colincross

colincross Mar 17, 2017
Contributor

We would need to define what the semantics are for comparing two maps whose keys are deepequal but not equal. Even for two maps that were deep-cloned off of each other, there is no guarantee that we can determine a 1-1 mapping between keys in the two maps.

The alternative is to sidestep this problem and refuse to deepcompare pointers or anything that might contain pointers (structs, arrays) as the keys in maps. This should be fine for the parser/printer, since they only support strings as the keys to maps.

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

Unfortunately the test needs to be able to compare SyntaxTree.comments, whose type is map[ParseNode](*CommentPair) , which uses an interface type as its key

I think that my inclination, if we want to spend the effort, would be two changes. 1. we fix the case in deepCompare involving maps having recursively equal keys such that deepCompare deems them equal, and 2. we require the individual tests to explicitly invoke deepCompare, so if the tests require certain relationships between the pointers themselves (currently the checks specific to the pointers are in the Builder class itself), then the tests can validate those requirements separately, and can still use deepCompare while doing it.

The changes to deepCompare I imagine would be to have it assume that if there exists a 1-1 mapping for which we cannot find a relevant difference, then we deem the data structures equivalent. In other words: 1. For each combination of entries in map1 and map2, check whether those two entries are equal. 2. For each entry A in map1 that has K matches in map2, confirm that there are exactly K such entries in map1 that have the same set of matches in map2 3. If every condition in 2 yielded true, then return true.

As for sidestepping the problem, my favorite option is to invoke the printer (or perhaps VerbosePrinter) in printer_test.go and check the text output.

Alternatively we could adjust the code being tested attach an integer id to each ParseNode and to use that as the map key rather than the ParseNode reference itself.

return item, false
}

func isIn(item reflect.Value, list []reflect.Value) (contained bool) {

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

I think this function (and findMatching and compareValues) are unnecessary due to comments below

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

See below about keys being pointers

b := expected.item
aLen := a.Len()
bLen := b.Len()
sharedLen := int(math.Min(float64(aLen), float64(bLen)))

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

Don't cast to float, either implement a trivial min function or open code it

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

ok

aLen := a.Len()
bLen := b.Len()
sharedLen := int(math.Min(float64(aLen), float64(bLen)))
for i := 0; i < sharedLen; i++ {

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

Or:
for i := 0; i < a.Len() && i < b.Len(); i++ {

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

I'll re-implement the 'min' function as you mentioned above.

I extracted the 'min' operation to slightly decrease the number of values being recomputed during each loop iteration (I don't see anything in https://github.com/golang/go/wiki/CompilerOptimizations specifying that the Go compiler can optimize 'i < a.Len() && i < b.Len()').

b := expected.item
aCount := a.NumField()
bCount := b.NumField()
if aCount != bCount {

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

It would be more reliable to compare the types here, or delete the check entirely

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

ok

return true, ""
}

// deepCompare is much like reflect.DeepEqual, but it does a deep comparison of map keys too (which is slower than a map lookup)

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

Deep comparing map keys seems unnecessary to me. If they hash to the same value they must be == equal, and if they are == equal then they must be DeepEqual. If that is not true, can you construct a test that returns different values for reflect.DeepEqual and deepCompare?

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

Yeah, I was surprised too. I'll add a test involving a map having keys of pointer type.

return deepCompareObjects(namedItem{"actual", reflect.ValueOf(actual)}, namedItem{"expected", reflect.ValueOf(expected)}, 0)
}

// deprecated

This comment has been minimized.

@colincross

colincross Mar 16, 2017
Contributor

Why check in a deprecated function?

This comment has been minimized.

@mathjeff

mathjeff Mar 17, 2017
Author Contributor

I'll remove it

Comments: comments,
}, errs

type parser struct {

This comment has been minimized.

@colincross

colincross Mar 17, 2017
Contributor

reordering these makes this really hard to review

This comment has been minimized.

@mathjeff

mathjeff Mar 22, 2017
Author Contributor

done

Name string
Defs []Definition
Comments []*CommentGroup
type ParseTree struct {

This comment has been minimized.

@colincross

colincross Mar 17, 2017
Contributor

The distinction between ParseTree and SyntaxTree is not clear to me. Leaving this as File and File.Name would be much easier to review, and then you can use a separate patch to do renames. The gorename command line tool is useful for doing programmatic renames, and you can include the gorename command in the commit message.

This comment has been minimized.

@mathjeff

mathjeff Mar 22, 2017
Author Contributor

done

@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch 3 times, most recently from 5f96b9c to d263470 Mar 22, 2017
Copy link
Contributor

@colincross colincross left a comment

Partial review of the first 2 commits

@@ -16,7 +16,7 @@ package parser

import (
"bytes"
"reflect"
"github.com/google/blueprint/proptools"

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

Imports for blueprint modules should go after all the imports from the standard library, with a blank line between the groups.

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

Have you made any customizations to Gogland that automate things like line lengths and import ordering? If so, I wonder if we can check them in somewhere.

This comment has been minimized.

@colincross

colincross May 17, 2017
Contributor

No, and it keeps screwing them up for me too.

expectedNodes := expected.ListOfAllNodes()
remappings = make(map[ParseNode]ParseNode, 0)
for i, actual := range actualNodes {
remappings[actual] = expectedNodes[i]

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

Might be worth at least adding sanity checks here. If len(expectedNodes) < len(actualNodes) this will panic

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

ok

proptools.NamedItem{"actual.SyntaxTree.Comments", analogousActualComments})
}

func compareSourcePositions(expected *ParseTree, actual *ParseTree, actualNode_remappings map[ParseNode]ParseNode) proptools.Comparison {

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

Go doesn't enforce a hard line length limit, but try to keep lines under 100 characters.

Also Go doesn't use underscores in variable names.

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

Yeah I don't like the underscore either; I just use it to clarify that this is the remappings of the actualNodes as opposed to being the actual nodeRemappings. What name would you recommend that conveys as much information?

This comment has been minimized.

@colincross

colincross May 6, 2017
Contributor

Sometimes its better to use a short, readable name and put the extra information in a comment, rather than carrying all the information everywhere you use the name. See https://github.com/golang/go/wiki/CodeReviewComments#variable-names

This comment has been minimized.

@mathjeff

mathjeff May 9, 2017
Author Contributor

Hmm it seems to me that the trouble in this case is more that I forgot to add a comment than that I used a long variable name.

In any case, I've now redone it in the next version of this commit for you to take a look at.


// now compare the comments
return proptools.DeepCompare(
proptools.NamedItem{"expected.SyntaxTree.Comments", expected.SyntaxTree.comments},

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

NamedItem isn't making the API any clearer, maybe just pass the string and tree as adjacent parameters

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

ok

// The reason we replace the keys is so proptools.DeepCompare can know which keys we want to compare against which others

analogousSourcePositions := make(map[ParseNode]scanner.Position)
for actualKey, actualValue := range expected.SourcePositions {

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

Should this be actual.SourcePositions?

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

true

if in[0] == '\n' {
in = in[1:]
} else {
panic(fmt.Sprintf("A newline at the beginning of testcase input is required, to make the test code easily readable. Offending test case '%s' starts with character: '%s'", description, string(in[0])))

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

This doesn't seem worth enforcing, just strip it if it is there.

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

ok

Item interface{}
}

type Comparison struct {

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

a two-value return is more idiomatic than returning a struct

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

When is that (two values preferable to a struct) the case? Is it when the two values have fundamentally different meanings? It seems to me that a Comparison is itself a concept. If we wanted to instead return an entire list of differences, it'd be convenient to not have to update the signatures of each method based on the implementation of a Comparison.

This comment has been minimized.

@colincross

colincross May 6, 2017
Contributor

I'd say that a struct should be used if you are going to pass that encapsulated value around (more than just returning it). If you are going to immediately unpack and operate on it, a multiple-value return is better.

This comment has been minimized.

@mathjeff

mathjeff May 9, 2017
Author Contributor

That logic sounds like a decent rule of thumb to me: to use a multi-valued return if the caller is likely to immediately read all of the contained fields. Does it apply here? None of the functions in compare.go read the field Comparison.Difference .

If for example we wanted to add another field to Comparison that perhaps specified the property path of the difference, then those fields probably wouldn't be read by anything in compare.go either.

This comment has been minimized.

@mathjeff

mathjeff May 10, 2017
Author Contributor

I talked to Dan and he thinks that if the number of fields is small then the thing to do is to make the fields be separate return values, and if the number of fields grows then to consider making the return be a struct at that time. I suppose that that's enough of a rule of thumb for me to be able to follow anyway, so I'll do that.

}
}

// compares two maps

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

document the semantics of this comparison, especially as compared to reflect.DeepEqual

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

done

return equal
}

// compares two arrays

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

compares two arrays or slices

document that this compares [0,len()), and not [0,cap())

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

ok

}
}

// compares two pointers in the same manner as reflect.deepEqual

This comment has been minimized.

@colincross

colincross Apr 11, 2017
Contributor

compares two pointers or interfaces

This comment has been minimized.

@mathjeff

mathjeff May 5, 2017
Author Contributor

done

@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch 2 times, most recently from 455e669 to 551628b May 5, 2017
@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch from 551628b to 0c49436 May 12, 2017
return false, explanation
}

// end of public members

This comment has been minimized.

@colincross

colincross May 17, 2017
Contributor

This seems both unnecessary and misleading, since same() and different() are not public

This comment has been minimized.

@mathjeff

mathjeff May 24, 2017
Author Contributor

oops

}
}
if len(differences) > 0 {
return firstDifference(differences)

This comment has been minimized.

@colincross

colincross May 17, 2017
Contributor

return false, sort.Strings(differences)[0]

This comment has been minimized.

@mathjeff

mathjeff May 24, 2017
Author Contributor

thanks

return same()
}

// compares two arrays (this ignores cap(), and only compares elements from indices 0 to len() )

This comment has been minimized.

@colincross

colincross May 17, 2017
Contributor

arrays or slices

This comment has been minimized.

@mathjeff

mathjeff May 24, 2017
Author Contributor

ok

// a map with several differences, for confirming determinism
map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9},
map[int]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 0},
`a[0(%!t(int=0))] = 0 whereas b[0(%!t(int=0))] = 1`,

This comment has been minimized.

@colincross

colincross May 17, 2017
Contributor

This seems like a bug?

This comment has been minimized.

@mathjeff

mathjeff May 24, 2017
Author Contributor

What would you prefer instead?

This comment has been minimized.

@colincross

colincross May 26, 2017
Contributor

These results contain %!t, which is an error inserted into the result of fmt.Sprintf. %t expects a bool, did you mean %T?

@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch 3 times, most recently from da366ce to 1e20165 May 24, 2017
p.requestNewlinesForPos(pos)
func (p *printer) beforePrint(node ParseNode) {
var comments = p.syntaxTree.GetCommentsIfPresent(node)
if comments != nil {

This comment has been minimized.

@colincross

colincross May 26, 2017
Contributor

Nil check is unnecessary, range on a nil slice is not an error

This comment has been minimized.

@mathjeff

mathjeff May 26, 2017
Author Contributor

Hmm, when I remove the check, I get a panic trying to access comments.preComments

// a map with several differences, for confirming determinism
map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9},
map[int]int{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 0},
`a[0(%!t(int=0))] = 0 whereas b[0(%!t(int=0))] = 1`,

This comment has been minimized.

@colincross

colincross May 26, 2017
Contributor

These results contain %!t, which is an error inserted into the result of fmt.Sprintf. %t expects a bool, did you mean %T?

@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch from 1e20165 to 8ac38b1 May 27, 2017
mathjeff added 4 commits Mar 8, 2017
this should help when making more test cases

Bug: b/34671474
Test: make blueprint_tools

Change-Id: Iff6197ac64053fde33119bbf59885c714b27c91f
A ParseTree represents a tree that was parsed from text, and also
includes information about where its nodes came from. Currently the
source information is attached to the nodes themselves, but that's
changing in a subsequent patch, at which point the ParseTree will
directly contain the source information. At that point, it will be
possible for a programmatic builder class to generate a syntax tree
without having to think about or pass in empty values for text
positions.

Really the reason that this patch is separate is to make the code review
more convenient.

gorename -from 'github.com/google/blueprint/parser.File.Name' -to 'FileName'
gorename -from 'github.com/google/blueprint/parser.File' -to 'ParseTree'

Bug: b/34671474
Test: make blueprint_tools

Change-Id: I7d591ac123ccd1697a3d739308596655afc39197
Now the final position of each node is now decided separately by the
printer during printing, based on the node's place in the SyntaxTree

The parser now generates a ParseTree, which has slightly more
information than a SyntaxTree - it contains both the syntax tree and the
original source positions of the tokens from which the syntax tree was
parsed

Bug: b/34671474
Test: make blueprint_tools

Change-Id: I55466114917cc88ac9e4e60b56bda2d4318c231a
…d by compare_test.go

Bug: b/34671474
Test: upload a patch for Travis to build

Change-Id: I1bb7f94104e904c09c4175c5c85164080c6756fc
@mathjeff mathjeff force-pushed the mathjeff:remove-pos branch from 8ac38b1 to 7971464 May 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
2 participants
You can’t perform that action at this time.