mirror of
https://github.com/golang/go
synced 2024-11-23 00:50:05 -07:00
cmd/compile: always accept 1.18 syntax but complain if not 1.18
This CL configures the parser to always accept 1.18 syntax (type parameters, type instantiations, interface elements), even when -lang is set to an earlier release. Instead, the type checker looks for 1.18 operations and complains if the language version is set to an earlier release. Doing these checks during type checking is necessary because it it is possible to write "generic" code using pre-1.18 syntax; for instance, an imported generic function may be implicitly instantiated (as in imported.Max(2, 3)), or an imported constraint interface may be embedded in an "ordinary" interface. Fixes #47818. Change-Id: I83ec302b3f4ba7196c0a4743c03670cfb901310d Reviewed-on: https://go-review.googlesource.com/c/go/+/344871 Trust: Robert Griesemer <gri@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
This commit is contained in:
parent
bf0bc4122f
commit
4068fb6c21
@ -35,7 +35,7 @@ func LoadPackage(filenames []string) {
|
|||||||
supportsGenerics := base.Flag.G != 0 || buildcfg.Experiment.Unified
|
supportsGenerics := base.Flag.G != 0 || buildcfg.Experiment.Unified
|
||||||
|
|
||||||
mode := syntax.CheckBranches
|
mode := syntax.CheckBranches
|
||||||
if supportsGenerics && types.AllowsGoVersion(types.LocalPkg, 1, 18) {
|
if supportsGenerics {
|
||||||
mode |= syntax.AllowGenerics
|
mode |= syntax.AllowGenerics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,10 @@ import (
|
|||||||
// funcInst type-checks a function instantiation inst and returns the result in x.
|
// funcInst type-checks a function instantiation inst and returns the result in x.
|
||||||
// The operand x must be the evaluation of inst.X and its type must be a signature.
|
// The operand x must be the evaluation of inst.X and its type must be a signature.
|
||||||
func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
|
func (check *Checker) funcInst(x *operand, inst *syntax.IndexExpr) {
|
||||||
|
if !check.allowVersion(check.pkg, 1, 18) {
|
||||||
|
check.softErrorf(inst.Pos(), "function instantiation requires go1.18 or later")
|
||||||
|
}
|
||||||
|
|
||||||
xlist := unpackExpr(inst.Index)
|
xlist := unpackExpr(inst.Index)
|
||||||
targs := check.typeList(xlist)
|
targs := check.typeList(xlist)
|
||||||
if targs == nil {
|
if targs == nil {
|
||||||
@ -318,6 +322,13 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
|
|||||||
|
|
||||||
// infer type arguments and instantiate signature if necessary
|
// infer type arguments and instantiate signature if necessary
|
||||||
if sig.TParams().Len() > 0 {
|
if sig.TParams().Len() > 0 {
|
||||||
|
if !check.allowVersion(check.pkg, 1, 18) {
|
||||||
|
if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil {
|
||||||
|
check.softErrorf(iexpr.Pos(), "function instantiation requires go1.18 or later")
|
||||||
|
} else {
|
||||||
|
check.softErrorf(call.Pos(), "implicit function instantiation requires go1.18 or later")
|
||||||
|
}
|
||||||
|
}
|
||||||
// TODO(gri) provide position information for targs so we can feed
|
// TODO(gri) provide position information for targs so we can feed
|
||||||
// it to the instantiate call for better error reporting
|
// it to the instantiate call for better error reporting
|
||||||
targs := check.infer(call.Pos(), sig.TParams().list(), targs, sigParams, args, true)
|
targs := check.infer(call.Pos(), sig.TParams().list(), targs, sigParams, args, true)
|
||||||
|
@ -514,11 +514,26 @@ func (check *Checker) varDecl(obj *Var, lhs []*Var, typ, init syntax.Expr) {
|
|||||||
check.initVars(lhs, []syntax.Expr{init}, nopos)
|
check.initVars(lhs, []syntax.Expr{init}, nopos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isImportedConstraint reports whether typ is an imported type constraint.
|
||||||
|
func (check *Checker) isImportedConstraint(typ Type) bool {
|
||||||
|
named, _ := typ.(*Named)
|
||||||
|
if named == nil || named.obj.pkg == check.pkg || named.obj.pkg == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
u, _ := named.under().(*Interface)
|
||||||
|
return u != nil && u.IsConstraint()
|
||||||
|
}
|
||||||
|
|
||||||
func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named) {
|
func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named) {
|
||||||
assert(obj.typ == nil)
|
assert(obj.typ == nil)
|
||||||
|
|
||||||
|
var rhs Type
|
||||||
check.later(func() {
|
check.later(func() {
|
||||||
check.validType(obj.typ, nil)
|
check.validType(obj.typ, nil)
|
||||||
|
// If typ is local, an error was already reported where typ is specified/defined.
|
||||||
|
if check.isImportedConstraint(rhs) && !check.allowVersion(check.pkg, 1, 18) {
|
||||||
|
check.errorf(tdecl.Type.Pos(), "using type constraint %s requires go1.18 or later", rhs)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
alias := tdecl.Alias
|
alias := tdecl.Alias
|
||||||
@ -540,7 +555,8 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
|
|||||||
}
|
}
|
||||||
|
|
||||||
obj.typ = Typ[Invalid]
|
obj.typ = Typ[Invalid]
|
||||||
obj.typ = check.anyType(tdecl.Type)
|
rhs = check.anyType(tdecl.Type)
|
||||||
|
obj.typ = rhs
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -555,8 +571,9 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named
|
|||||||
}
|
}
|
||||||
|
|
||||||
// determine underlying type of named
|
// determine underlying type of named
|
||||||
named.fromRHS = check.definedType(tdecl.Type, named)
|
rhs = check.definedType(tdecl.Type, named)
|
||||||
assert(named.fromRHS != nil)
|
assert(rhs != nil)
|
||||||
|
named.fromRHS = rhs
|
||||||
// The underlying type of named may be itself a named type that is
|
// The underlying type of named may be itself a named type that is
|
||||||
// incomplete:
|
// incomplete:
|
||||||
//
|
//
|
||||||
|
@ -412,52 +412,60 @@ func (check *Checker) collectObjects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *syntax.TypeDecl:
|
case *syntax.TypeDecl:
|
||||||
|
if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) {
|
||||||
|
check.softErrorf(s.TParamList[0], "type parameters require go1.18 or later")
|
||||||
|
}
|
||||||
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil)
|
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Value, nil)
|
||||||
check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s})
|
check.declarePkgObj(s.Name, obj, &declInfo{file: fileScope, tdecl: s})
|
||||||
|
|
||||||
case *syntax.FuncDecl:
|
case *syntax.FuncDecl:
|
||||||
d := s // TODO(gri) get rid of this
|
name := s.Name.Value
|
||||||
name := d.Name.Value
|
obj := NewFunc(s.Name.Pos(), pkg, name, nil)
|
||||||
obj := NewFunc(d.Name.Pos(), pkg, name, nil)
|
hasTParamError := false // avoid duplicate type parameter errors
|
||||||
if d.Recv == nil {
|
if s.Recv == nil {
|
||||||
// regular function
|
// regular function
|
||||||
if name == "init" || name == "main" && pkg.name == "main" {
|
if name == "init" || name == "main" && pkg.name == "main" {
|
||||||
if d.TParamList != nil {
|
if len(s.TParamList) != 0 {
|
||||||
check.softErrorf(d, "func %s must have no type parameters", name)
|
check.softErrorf(s.TParamList[0], "func %s must have no type parameters", name)
|
||||||
|
hasTParamError = true
|
||||||
}
|
}
|
||||||
if t := d.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 {
|
if t := s.Type; len(t.ParamList) != 0 || len(t.ResultList) != 0 {
|
||||||
check.softErrorf(d, "func %s must have no arguments and no return values", name)
|
check.softErrorf(s, "func %s must have no arguments and no return values", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// don't declare init functions in the package scope - they are invisible
|
// don't declare init functions in the package scope - they are invisible
|
||||||
if name == "init" {
|
if name == "init" {
|
||||||
obj.parent = pkg.scope
|
obj.parent = pkg.scope
|
||||||
check.recordDef(d.Name, obj)
|
check.recordDef(s.Name, obj)
|
||||||
// init functions must have a body
|
// init functions must have a body
|
||||||
if d.Body == nil {
|
if s.Body == nil {
|
||||||
// TODO(gri) make this error message consistent with the others above
|
// TODO(gri) make this error message consistent with the others above
|
||||||
check.softErrorf(obj.pos, "missing function body")
|
check.softErrorf(obj.pos, "missing function body")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
check.declare(pkg.scope, d.Name, obj, nopos)
|
check.declare(pkg.scope, s.Name, obj, nopos)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// method
|
// method
|
||||||
// d.Recv != nil
|
// d.Recv != nil
|
||||||
if !acceptMethodTypeParams && len(d.TParamList) != 0 {
|
if !acceptMethodTypeParams && len(s.TParamList) != 0 {
|
||||||
//check.error(d.TParamList.Pos(), invalidAST + "method must have no type parameters")
|
//check.error(d.TParamList.Pos(), invalidAST + "method must have no type parameters")
|
||||||
check.error(d, invalidAST+"method must have no type parameters")
|
check.error(s.TParamList[0], invalidAST+"method must have no type parameters")
|
||||||
|
hasTParamError = true
|
||||||
}
|
}
|
||||||
ptr, recv, _ := check.unpackRecv(d.Recv.Type, false)
|
ptr, recv, _ := check.unpackRecv(s.Recv.Type, false)
|
||||||
// (Methods with invalid receiver cannot be associated to a type, and
|
// (Methods with invalid receiver cannot be associated to a type, and
|
||||||
// methods with blank _ names are never found; no need to collect any
|
// methods with blank _ names are never found; no need to collect any
|
||||||
// of them. They will still be type-checked with all the other functions.)
|
// of them. They will still be type-checked with all the other functions.)
|
||||||
if recv != nil && name != "_" {
|
if recv != nil && name != "_" {
|
||||||
methods = append(methods, methodInfo{obj, ptr, recv})
|
methods = append(methods, methodInfo{obj, ptr, recv})
|
||||||
}
|
}
|
||||||
check.recordDef(d.Name, obj)
|
check.recordDef(s.Name, obj)
|
||||||
}
|
}
|
||||||
info := &declInfo{file: fileScope, fdecl: d}
|
if len(s.TParamList) != 0 && !check.allowVersion(pkg, 1, 18) && !hasTParamError {
|
||||||
|
check.softErrorf(s.TParamList[0], "type parameters require go1.18 or later")
|
||||||
|
}
|
||||||
|
info := &declInfo{file: fileScope, fdecl: s}
|
||||||
// Methods are not package-level objects but we still track them in the
|
// Methods are not package-level objects but we still track them in the
|
||||||
// object map so that we can handle them like regular functions (if the
|
// object map so that we can handle them like regular functions (if the
|
||||||
// receiver is invalid); also we need their fdecl info when associating
|
// receiver is invalid); also we need their fdecl info when associating
|
||||||
|
@ -38,7 +38,7 @@ func (m substMap) lookup(tpar *TypeParam) Type {
|
|||||||
// subst returns the type typ with its type parameters tpars replaced by the
|
// subst returns the type typ with its type parameters tpars replaced by the
|
||||||
// corresponding type arguments targs, recursively. subst doesn't modify the
|
// corresponding type arguments targs, recursively. subst doesn't modify the
|
||||||
// incoming type. If a substitution took place, the result type is different
|
// incoming type. If a substitution took place, the result type is different
|
||||||
// from from the incoming type.
|
// from the incoming type.
|
||||||
//
|
//
|
||||||
// If the given typMap is non-nil, it is used in lieu of check.typMap.
|
// If the given typMap is non-nil, it is used in lieu of check.typMap.
|
||||||
func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, typMap map[string]*Named) Type {
|
func (check *Checker) subst(pos syntax.Pos, typ Type, smap substMap, typMap map[string]*Named) Type {
|
||||||
|
@ -146,7 +146,7 @@ type (
|
|||||||
m1(I5)
|
m1(I5)
|
||||||
}
|
}
|
||||||
I6 interface {
|
I6 interface {
|
||||||
S0 /* ERROR "not an interface" */
|
S0 /* ERROR "non-interface type S0" */
|
||||||
}
|
}
|
||||||
I7 interface {
|
I7 interface {
|
||||||
I1
|
I1
|
||||||
|
@ -79,11 +79,11 @@ func issue9473(a []int, b ...int) {
|
|||||||
// Check that embedding a non-interface type in an interface results in a good error message.
|
// Check that embedding a non-interface type in an interface results in a good error message.
|
||||||
func issue10979() {
|
func issue10979() {
|
||||||
type _ interface {
|
type _ interface {
|
||||||
int /* ERROR int is not an interface */
|
int /* ERROR non-interface type int */
|
||||||
}
|
}
|
||||||
type T struct{}
|
type T struct{}
|
||||||
type _ interface {
|
type _ interface {
|
||||||
T /* ERROR T is not an interface */
|
T /* ERROR non-interface type T */
|
||||||
}
|
}
|
||||||
type _ interface {
|
type _ interface {
|
||||||
nosuchtype /* ERROR undeclared name: nosuchtype */
|
nosuchtype /* ERROR undeclared name: nosuchtype */
|
||||||
@ -280,7 +280,7 @@ type issue25301b /* ERROR cycle */ = interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type issue25301c interface {
|
type issue25301c interface {
|
||||||
notE // ERROR struct\{\} is not an interface
|
notE // ERROR non-interface type struct\{\}
|
||||||
}
|
}
|
||||||
|
|
||||||
type notE = struct{}
|
type notE = struct{}
|
||||||
|
@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func /* ERROR "func main must have no type parameters" */ main[T any]() {}
|
func main [T /* ERROR "func main must have no type parameters" */ any]() {}
|
||||||
|
@ -304,8 +304,8 @@ var _ = f8[int, float64](0, 0, nil...) // test case for #18268
|
|||||||
// init functions cannot have type parameters
|
// init functions cannot have type parameters
|
||||||
|
|
||||||
func init() {}
|
func init() {}
|
||||||
func init[/* ERROR func init must have no type parameters */ _ any]() {}
|
func init[_ /* ERROR func init must have no type parameters */ any]() {}
|
||||||
func init[/* ERROR func init must have no type parameters */ P any]() {}
|
func init[P /* ERROR func init must have no type parameters */ any]() {}
|
||||||
|
|
||||||
type T struct {}
|
type T struct {}
|
||||||
|
|
||||||
|
59
src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2
vendored
Normal file
59
src/cmd/compile/internal/types2/testdata/fixedbugs/issue47818.go2
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Parser accepts type parameters but the type checker
|
||||||
|
// needs to report any operations that are not permitted
|
||||||
|
// before Go 1.18.
|
||||||
|
|
||||||
|
package go1_17
|
||||||
|
|
||||||
|
type T[P /* ERROR type parameters require go1\.18 or later */ any] struct{}
|
||||||
|
|
||||||
|
// for init (and main, but we're not in package main) we should only get one error
|
||||||
|
func init[P /* ERROR func init must have no type parameters */ any]() {}
|
||||||
|
func main[P /* ERROR type parameters require go1\.18 or later */ any]() {}
|
||||||
|
|
||||||
|
func f[P /* ERROR type parameters require go1\.18 or later */ any](x P) {
|
||||||
|
var _ T[ /* ERROR type instantiation requires go1\.18 or later */ int]
|
||||||
|
var _ (T[ /* ERROR type instantiation requires go1\.18 or later */ int])
|
||||||
|
_ = T[ /* ERROR type instantiation requires go1\.18 or later */ int]{}
|
||||||
|
_ = T[ /* ERROR type instantiation requires go1\.18 or later */ int](struct{}{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (T[ /* ERROR type instantiation requires go1\.18 or later */ P]) g(x int) {
|
||||||
|
f[ /* ERROR function instantiation requires go1\.18 or later */ int](0) // explicit instantiation
|
||||||
|
(f[ /* ERROR function instantiation requires go1\.18 or later */ int])(0) // parentheses (different code path)
|
||||||
|
f( /* ERROR implicit function instantiation requires go1\.18 or later */ x) // implicit instantiation
|
||||||
|
}
|
||||||
|
|
||||||
|
type C1 interface {
|
||||||
|
comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\)
|
||||||
|
}
|
||||||
|
|
||||||
|
type C2 interface {
|
||||||
|
comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\)
|
||||||
|
int // ERROR embedding non-interface type int requires go1\.18 or later
|
||||||
|
~ /* ERROR embedding interface element ~int requires go1\.18 or later */ int
|
||||||
|
int /* ERROR embedding interface element int\|~string requires go1\.18 or later */ | ~string
|
||||||
|
}
|
||||||
|
|
||||||
|
type _ interface {
|
||||||
|
// errors for these were reported with their declaration
|
||||||
|
C1
|
||||||
|
C2
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
_ comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\)
|
||||||
|
// errors for these were reported with their declaration
|
||||||
|
_ C1
|
||||||
|
_ C2
|
||||||
|
|
||||||
|
_ = comparable // ERROR undeclared name: comparable \(requires version go1\.18 or later\)
|
||||||
|
// errors for these were reported with their declaration
|
||||||
|
_ = C1
|
||||||
|
_ = C2
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO(gri) need test cases for imported constraint types (see also issue #47967)
|
@ -271,6 +271,11 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
switch u := under(typ).(type) {
|
switch u := under(typ).(type) {
|
||||||
case *Interface:
|
case *Interface:
|
||||||
tset := computeInterfaceTypeSet(check, pos, u)
|
tset := computeInterfaceTypeSet(check, pos, u)
|
||||||
|
// If typ is local, an error was already reported where typ is specified/defined.
|
||||||
|
if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, 1, 18) {
|
||||||
|
check.errorf(pos, "embedding constraint interface %s requires go1.18 or later", typ)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if tset.comparable {
|
if tset.comparable {
|
||||||
ityp.tset.comparable = true
|
ityp.tset.comparable = true
|
||||||
}
|
}
|
||||||
@ -279,6 +284,10 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
}
|
}
|
||||||
terms = tset.terms
|
terms = tset.terms
|
||||||
case *Union:
|
case *Union:
|
||||||
|
if check != nil && !check.allowVersion(check.pkg, 1, 18) {
|
||||||
|
check.errorf(pos, "embedding interface element %s requires go1.18 or later", u)
|
||||||
|
continue
|
||||||
|
}
|
||||||
tset := computeUnionTypeSet(check, pos, u)
|
tset := computeUnionTypeSet(check, pos, u)
|
||||||
if tset == &invalidTypeSet {
|
if tset == &invalidTypeSet {
|
||||||
continue // ignore invalid unions
|
continue // ignore invalid unions
|
||||||
@ -293,7 +302,7 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if check != nil && !check.allowVersion(check.pkg, 1, 18) {
|
if check != nil && !check.allowVersion(check.pkg, 1, 18) {
|
||||||
check.errorf(pos, "%s is not an interface", typ)
|
check.errorf(pos, "embedding non-interface type %s requires go1.18 or later", typ)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
terms = termlist{{false, typ}}
|
terms = termlist{{false, typ}}
|
||||||
|
@ -38,14 +38,12 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType boo
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case universeAny, universeComparable:
|
case universeAny, universeComparable:
|
||||||
|
// complain if necessary but keep going
|
||||||
if !check.allowVersion(check.pkg, 1, 18) {
|
if !check.allowVersion(check.pkg, 1, 18) {
|
||||||
check.errorf(e, "undeclared name: %s (requires version go1.18 or later)", e.Value)
|
check.softErrorf(e, "undeclared name: %s (requires version go1.18 or later)", e.Value)
|
||||||
return
|
} else if obj == universeAny {
|
||||||
}
|
|
||||||
// If we allow "any" for general use, this if-statement can be removed (issue #33232).
|
// If we allow "any" for general use, this if-statement can be removed (issue #33232).
|
||||||
if obj == universeAny {
|
check.softErrorf(e, "cannot use any outside constraint position")
|
||||||
check.error(e, "cannot use any outside constraint position")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
check.recordUse(e, obj)
|
check.recordUse(e, obj)
|
||||||
@ -274,6 +272,9 @@ func (check *Checker) typInternal(e0 syntax.Expr, def *Named) (T Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case *syntax.IndexExpr:
|
case *syntax.IndexExpr:
|
||||||
|
if !check.allowVersion(check.pkg, 1, 18) {
|
||||||
|
check.softErrorf(e.Pos(), "type instantiation requires go1.18 or later")
|
||||||
|
}
|
||||||
return check.instantiatedType(e.X, unpackExpr(e.Index), def)
|
return check.instantiatedType(e.X, unpackExpr(e.Index), def)
|
||||||
|
|
||||||
case *syntax.ParenExpr:
|
case *syntax.ParenExpr:
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
type I interface {
|
type I interface {
|
||||||
int // ERROR "interface contains embedded non-interface|not an interface"
|
int // ERROR "interface contains embedded non-interface|embedding non-interface type"
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() I {
|
func New() I {
|
||||||
|
Loading…
Reference in New Issue
Block a user