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

Handle dropped fields in methods #31

Merged
merged 6 commits into from
Feb 19, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
303 changes: 271 additions & 32 deletions driver/normalizer/normalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,8 @@ func (op opArrToChain) Construct(st *State, n nodes.Node) (nodes.Node, error) {
return typ, nil
}

func funcDefMap(typ string, returns bool, other Obj) Mapping {
func funcDefMap(typ string, returns bool, other Obj, toGroup ...Op) Mapping {
dennwc marked this conversation as resolved.
Show resolved Hide resolved
src := Obj{
"Body": Var("body"),
"Identifier": Var("name"),
"ParameterList": Obj{
uast.KeyType: String("ParameterList"),
Expand All @@ -183,11 +182,15 @@ func funcDefMap(typ string, returns bool, other Obj) Mapping {
"IsMissing": Bool(false),
"IsStructuredTrivia": Bool(false),
"SemicolonToken": Any(),

// FIXME(dennwc): driver drops them currently
"AttributeLists": Any(),
"ExpressionBody": Any(),
"Modifiers": Any(),
"AttributeLists": Cases("caseAttrs",
Arr(),
Check(OfKind(nodes.KindArray), Var("attr")),
Check(OfKind(nodes.KindObject), Var("attr")),
),
"Modifiers": Cases("caseMods",
Arr(),
NotEmpty(Var("modifiers")),
),
}
for k, v := range other {
src[k] = v
Expand All @@ -203,19 +206,81 @@ func funcDefMap(typ string, returns bool, other Obj) Mapping {
}),
)
}
funcGroup := []Op{
Cases("caseAttrs",
Is(nil),
Var("attr"),
Arr(Var("attr")),
),
Cases("caseMods",
Is(nil),
NotEmpty(Var("modifiers")),
),
}
funcGroup = append(funcGroup, toGroup...)
funcGroup = append(funcGroup, UASTType(uast.Alias{}, Obj{
"Name": Var("name"),
"Node": UASTType(uast.Function{}, Obj{
"Type": UASTType(uast.FunctionType{}, dstType),
// If the function was defined with an arrow expression, we will
// generate a uast:Block with a since csharp:Return node containing
// the expression.
"Body": Cases("isArrow",
// case 1: arrow expression
UASTType(uast.Block{}, Obj{
uast.KeyPos: Var("arrow_pos"),
"Statements": Arr(
Obj{
uast.KeyType: String("ReturnStatement"),
uast.KeyPos: Var("arrow_pos_tok"),
"Expression": Var("arrow"),
},
),
}),
// case 2: full body
// TODO(dennwc): this will definitely fail the reverse transform
// make a more specific node check when we need it
// see https://github.com/bblfsh/sdk/issues/355
Var("body"),
),
}),
}))
return MapSemantic(typ, uast.FunctionGroup{}, MapObj(
src,
// Either Body or ExpressionBody will be set.
CasesObj("isArrow",
src,
Objs{
// case 1: arrow expression
{
"Body": Is(nil),
"ExpressionBody": Obj{
uast.KeyType: String("ArrowExpressionClause"),
// will use this positions for Block in Body
uast.KeyPos: Var("arrow_pos"),
"ArrowToken": Obj{
uast.KeyType: String("EqualsGreaterThanToken"),
// will use this position for Return in Body
uast.KeyPos: Var("arrow_pos_tok"),
"IsMissing": Bool(false),
"Text": Any(),
"Value": Any(),
"ValueText": Any(),
},
"IsMissing": Bool(false),
"IsStructuredTrivia": Bool(false),
"Expression": Var("arrow"),
},
},
// case 2: full body
{
"ExpressionBody": Is(nil),
"Body": Var("body"),
},
},
),

Obj{
"Nodes": Arr(
UASTType(uast.Alias{}, Obj{
"Name": Var("name"),
"Node": UASTType(uast.Function{}, Obj{
"Body": Var("body"),
"Type": UASTType(uast.FunctionType{}, dstType),
}),
}),
),
"Nodes": Arr(funcGroup...),
},
))
}
Expand Down Expand Up @@ -793,25 +858,128 @@ var Normalizers = []Mapping{
},
)),

funcDefMap("MethodDeclaration", true, Obj{
// FIXME(dennwc): driver drops them currently
"Arity": Any(),
"ConstraintClauses": Any(),
"ExplicitInterfaceSpecifier": Any(),
"TypeParameterList": Any(),
}),
funcDefMap("ConstructorDeclaration", false, Obj{
// FIXME(dennwc): driver drops them currently
"Initializer": Any(),
}),
funcDefMap("MethodDeclaration", true,
Obj{
// number of parameters - safe to ignore
"Arity": Any(),
"ExplicitInterfaceSpecifier": Is(nil),
"ConstraintClauses": Cases("caseConstraint",
Arr(),
NotEmpty(Var("constraints")),
),
"TypeParameterList": Cases("caseTypeParams",
Is(nil),
Arr(),
NotEmpty(Var("typeParams")),
),
},
Cases("caseTypeParams",
Is(nil),
Is(nil),
NotEmpty(Var("typeParams")),
),
Cases("caseConstraint",
Is(nil),
NotEmpty(Var("constraints")),
),
),
// ConstructorDeclaration is similar to MethodDeclaration, but it may include a
// base class initializer that require a special transformation.
MapSemantic("ConstructorDeclaration", uast.FunctionGroup{}, MapObj(
Obj{
// Same as to MethodDeclaration above
"Identifier": Var("name"),
"ParameterList": Obj{
uast.KeyType: String("ParameterList"),
uast.KeyPos: Any(),
"OpenParenToken": Any(),
creachadair marked this conversation as resolved.
Show resolved Hide resolved
"CloseParenToken": Any(),
"IsMissing": Bool(false),
"IsStructuredTrivia": Bool(false),
"Parameters": Var("params"),
},
"IsMissing": Bool(false),
"IsStructuredTrivia": Bool(false),
"SemicolonToken": Any(),
"AttributeLists": Cases("caseAttrs",
Arr(),
Check(OfKind(nodes.KindArray), Var("attr")),
Check(OfKind(nodes.KindObject), Var("attr")),
),

// won't be set for a constructor
"ExpressionBody": Is(nil),

// Initializer is an expression to initialize the base class.
// Here we consider case if it's exists in the AST node.
//
// Initializer is basically a function call that will init the base class.
// So we will consider it a first statement of a function body.
//
// For this we dig into Body above to get the list of statements.
// If hasBaseInit is set (initializer exists), we will prepend it
// to the array of statement in Body.
"Initializer": If("hasBaseInit",
// case 1: has base initializer
Check(HasType("BaseConstructorInitializer"), Var("baseInit")),
// case 2: no initializer
Is(nil),
),
"Body": Part("bodyMap", Obj{
"Statements": Var("stmts"),
}),
"Modifiers": Cases("caseMods",
Arr(),
NotEmpty(Var("modifiers")),
),
},
Obj{
"Nodes": Arr(
Cases("caseAttrs",
Is(nil),
Var("attr"),
Arr(Var("attr")),
),
Cases("caseMods",
Is(nil),
NotEmpty(Var("modifiers")),
),
UASTType(uast.Alias{}, Obj{
"Name": Var("name"),
"Node": UASTType(uast.Function{}, Obj{
// Restore the function body.
//
// We will also prepend a base class initializer here, see above.
"Body": Part("bodyMap", Obj{
"Statements": If("hasBaseInit",
// case 1: has base initializer
PrependOne(Var("baseInit"), Var("stmts")),
// case 2: no initializer
Var("stmts"),
),
}),
"Type": UASTType(uast.FunctionType{}, Obj{
"Arguments": Var("params"),
}),
}),
}),
),
},
)),
funcDefMap("DestructorDeclaration", false, Obj{
"TildeToken": Any(),
}),

// Merge uast:Group with uast:FunctionGroup.
Map(
opMergeGroups{Var("group")},
Check(Has{uast.KeyType: String(uast.TypeOf(uast.FunctionGroup{}))}, Var("group")),
Check(
Has{uast.KeyType: In(
nodes.String(typeFuncGroup),
nodes.String(typeGroup),
)},
Var("group"),
),
),
}

Expand Down Expand Up @@ -1019,13 +1187,26 @@ func (op opMergeGroups) Kinds() nodes.Kind {
return nodes.KindObject
}

// Check tests if the current node is uast:Group and if it contains a uast:FunctionGroup
// node, it will remove the current node and merge other children into the FunctionGroup.
// Check tests if a current node is uast:Group and uast:FuncGroup and contains group of
// another kind. It will remove the second group and merge children into current one.
// uast:FuncGroup is preferred.
dennwc marked this conversation as resolved.
Show resolved Hide resolved
func (op opMergeGroups) Check(st *State, n nodes.Node) (bool, error) {
group, ok := n.(nodes.Object)
if !ok || uast.TypeOf(group) != typeGroup {
if !ok {
return false, nil
}
switch uast.TypeOf(group) {
case typeGroup:
return op.checkGroup(st, group)
case typeFuncGroup:
return op.checkFuncGroup(st, group)
}
return false, nil
}

// checkGroup tests if the current node is uast:Group and if it contains a uast:FunctionGroup
dennwc marked this conversation as resolved.
Show resolved Hide resolved
// node, it will remove the current node and merge other children into the FunctionGroup.
func (op opMergeGroups) checkGroup(st *State, group nodes.Object) (bool, error) {
arr, ok := group["Nodes"].(nodes.Array)
if !ok {
return false, errors.New("expected an array in Group.Nodes")
Expand Down Expand Up @@ -1055,6 +1236,64 @@ func (op opMergeGroups) Check(st *State, n nodes.Node) (bool, error) {
return op.sub.Check(st, fgroup)
}

// checkFuncGroup tests if the current node is uast:FuncGroup and if any of its sub-arrays
// contain a ust:Group, it will be removed and the children will be flattened into a sub-array.
dennwc marked this conversation as resolved.
Show resolved Hide resolved
func (op opMergeGroups) checkFuncGroup(st *State, fgroup nodes.Object) (bool, error) {
// primary nodes array in the function group
arr, ok := fgroup["Nodes"].(nodes.Array)
if !ok {
return false, errors.New("expected an array in FuncGroup.Nodes")
}
modified := false
for i := 0; i < len(arr); i++ {
v := arr[i]
if v == nil {
// remove nil elements
dennwc marked this conversation as resolved.
Show resolved Hide resolved
if !modified {
arr = arr.CloneList()
modified = true
}
arr = append(arr[:i], arr[i+1:]...)
i--
continue
}
// secondary arrays the group annotations/modifiers, etc
arr2, ok := v.(nodes.Array)
if !ok {
continue
}
// find a group node there
ind := firstWithType(arr2, func(typ string) bool {
return typ == typeGroup
})
if ind < 0 {
continue
}
group := arr2[ind].(nodes.Object)
// children array of an inner group
arr3, ok := group["Nodes"].(nodes.Array)
if !ok {
return false, errors.New("expected an array in Group.Nodes")
}
// flatten inner array into the secondary array
out := make(nodes.Array, 0, len(arr2)-1+len(arr3))
out = append(out, arr2[:ind]...)
out = append(out, arr3...)
out = append(out, arr2[ind+1:]...)
if !modified {
arr = arr.CloneList()
modified = true
}
arr[i] = out
}
if !modified {
return false, nil
}
fgroup = fgroup.CloneObject()
fgroup["Nodes"] = arr
return op.sub.Check(st, fgroup)
}

func (op opMergeGroups) Construct(st *State, n nodes.Node) (nodes.Node, error) {
// TODO(dennwc): implement when we will need a reversal
// see https://github.com/bblfsh/sdk/issues/355
Expand Down
Loading