Skip to content

Commit 7bd700f

Browse files
authored
lowering,new: improve implementation robustness (#32404)
- move implementation of splatnew into lowering instead of dispatch (given the current definition of splatnew) - use field indexes with fieldtype instead of names - use fieldtype in most cases, instead of copying expression tree - explicit syntax error for too many type parameters on new (instead of deferring to the runtime apply_type error)
1 parent b2304c5 commit 7bd700f

File tree

4 files changed

+87
-48
lines changed

4 files changed

+87
-48
lines changed

base/essentials.jl

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -311,13 +311,6 @@ convert(::Type{Tuple{Vararg{V}}}, x::Tuple{Vararg{V}}) where {V} = x
311311
convert(T::Type{Tuple{Vararg{V}}}, x::Tuple) where {V} =
312312
(convert(tuple_type_head(T), x[1]), convert(T, tail(x))...)
313313

314-
# used for splatting in `new`
315-
convert_prefix(::Type{Tuple{}}, x::Tuple) = x
316-
convert_prefix(::Type{<:AtLeast1}, x::Tuple{}) = x
317-
convert_prefix(::Type{T}, x::T) where {T<:AtLeast1} = x
318-
convert_prefix(::Type{T}, x::AtLeast1) where {T<:AtLeast1} =
319-
(convert(tuple_type_head(T), x[1]), convert_prefix(tuple_type_tail(T), tail(x))...)
320-
321314
# TODO: the following definitions are equivalent (behaviorally) to the above method
322315
# I think they may be faster / more efficient for inference,
323316
# if we could enable them, but are they?

src/julia-syntax.scm

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -693,46 +693,52 @@
693693
,@locs
694694
(call (curly ,name ,@params) ,@field-names)))))
695695

696-
(define (new-call Tname type-params params args field-names field-types)
696+
(define (new-call Tname type-params sparams params args field-names field-types)
697697
(if (any kwarg? args)
698698
(error "\"new\" does not accept keyword arguments"))
699699
(if (length> params (length type-params))
700700
(error "too few type parameters specified in \"new{...}\""))
701-
(let ((Texpr (if (null? type-params)
702-
`(outerref ,Tname)
703-
`(curly (outerref ,Tname)
704-
,@type-params))))
701+
(if (length> type-params (length params))
702+
(error "too many type parameters specified in \"new{...}\""))
703+
(let* ((Texpr (if (null? type-params)
704+
`(outerref ,Tname)
705+
`(curly (outerref ,Tname)
706+
,@type-params)))
707+
(tn (make-ssavalue))
708+
(field-convert (lambda (fld fty val)
709+
(if (equal? fty '(core Any))
710+
val
711+
`(call (top convert)
712+
,(if (and (equal? type-params params) (memq fty params) (memq fty sparams))
713+
fty ; the field type is a simple parameter, the usage here is of a
714+
; local variable (currently just handles sparam) for the bijection of params to type-params
715+
`(call (core fieldtype) ,tn ,(+ fld 1)))
716+
,val)))))
705717
(cond ((length> (filter (lambda (a) (not (vararg? a))) args) (length field-names))
706718
`(call (core throw) (call (top ArgumentError)
707719
,(string "new: too many arguments (expected " (length field-names) ")"))))
708720
((any vararg? args)
709721
(if (every (lambda (ty) (equal? ty '(core Any)))
710722
field-types)
711723
`(splatnew ,Texpr (call (core tuple) ,@args))
712-
(let ((tn (make-ssavalue)))
724+
(let ((argt (make-ssavalue))
725+
(nf (make-ssavalue)))
713726
`(block
714727
(= ,tn ,Texpr)
715-
(splatnew ,tn (call (top convert_prefix)
716-
(curly (core Tuple)
717-
,@(map (lambda (fld)
718-
`(call (core fieldtype) ,tn (quote ,fld)))
719-
field-names))
720-
(call (core tuple) ,@args)))))))
728+
(= ,argt (call (core tuple) ,@args))
729+
(= ,nf (call (core nfields) ,argt))
730+
(if (call (top ult_int) ,nf ,(length field-names))
731+
(call (core throw) (call (top ArgumentError)
732+
,(string "new: too few arguments (expected " (length field-names) ")"))))
733+
(if (call (top ult_int) ,(length field-names) ,nf)
734+
(call (core throw) (call (top ArgumentError)
735+
,(string "new: too many arguments (expected " (length field-names) ")"))))
736+
(new ,tn ,@(map (lambda (fld fty) (field-convert fld fty `(call (core getfield) ,argt ,(+ fld 1) false)))
737+
(iota (length field-names)) (list-head field-types (length field-names))))))))
721738
(else
722-
(if (equal? type-params params)
723-
`(new ,Texpr ,@(map (lambda (fty val)
724-
(if (equal? fty '(core Any))
725-
val
726-
`(call (top convert) ,fty ,val)))
727-
(list-head field-types (length args)) args))
728-
(let ((tn (make-ssavalue)))
729-
`(block
730-
(= ,tn ,Texpr)
731-
(new ,tn ,@(map (lambda (fld val)
732-
`(call (top convert)
733-
(call (core fieldtype) ,tn (quote ,fld))
734-
,val))
735-
(list-head field-names (length args)) args)))))))))
739+
`(block
740+
(= ,tn ,Texpr)
741+
(new ,tn ,@(map field-convert (iota (length args)) (list-head field-types (length args)) args)))))))
736742

737743
;; insert item at start of arglist
738744
(define (arglist-unshift sig item)
@@ -745,10 +751,11 @@
745751
((length= lno 3) (string " around " (caddr lno) ":" (cadr lno)))
746752
(else "")))
747753

748-
(define (ctor-def name Tname params bounds sig ctor-body body wheres)
754+
(define (ctor-def name Tname ctor-body sig body wheres)
749755
(let* ((curly? (and (pair? name) (eq? (car name) 'curly)))
750756
(curlyargs (if curly? (cddr name) '()))
751-
(name (if curly? (cadr name) name)))
757+
(name (if curly? (cadr name) name))
758+
(sparams (map car (map analyze-typevar wheres))))
752759
(cond ((not (eq? name Tname))
753760
`(function ,(with-wheres `(call ,(if curly?
754761
`(curly ,name ,@curlyargs)
@@ -757,47 +764,47 @@
757764
wheres)
758765
;; pass '() in order to require user-specified parameters with
759766
;; new{...} inside a non-ctor inner definition.
760-
,(ctor-body body '())))
767+
,(ctor-body body '() sparams)))
761768
(else
762769
`(function ,(with-wheres `(call ,(if curly?
763770
`(curly ,name ,@curlyargs)
764771
name)
765772
,@sig)
766773
wheres)
767-
,(ctor-body body curlyargs))))))
774+
,(ctor-body body curlyargs sparams))))))
768775

769776
(define (function-body-lineno body)
770777
(let ((lnos (filter linenum? body)))
771778
(if (null? lnos) '() (car lnos))))
772779

773780
;; rewrite calls to `new( ... )` to `new` expressions on the appropriate
774781
;; type, determined by the containing constructor definition.
775-
(define (rewrite-ctor ctor Tname params bounds field-names field-types)
776-
(define (ctor-body body type-params)
782+
(define (rewrite-ctor ctor Tname params field-names field-types)
783+
(define (ctor-body body type-params sparams)
777784
(pattern-replace (pattern-set
778785
(pattern-lambda
779786
(call (-/ new) . args)
780-
(new-call Tname type-params params
781-
(map (lambda (a) (ctor-body a type-params)) args)
787+
(new-call Tname type-params sparams params
788+
(map (lambda (a) (ctor-body a type-params sparams)) args)
782789
field-names field-types))
783790
(pattern-lambda
784791
(call (curly (-/ new) . p) . args)
785-
(new-call Tname p params
786-
(map (lambda (a) (ctor-body a type-params)) args)
792+
(new-call Tname p sparams params
793+
(map (lambda (a) (ctor-body a type-params sparams)) args)
787794
field-names field-types)))
788795
body))
789796
(pattern-replace
790797
(pattern-set
791798
;; definitions without `where`
792799
(pattern-lambda (function (-$ (call name . sig) (|::| (call name . sig) _t)) body)
793-
(ctor-def name Tname params bounds sig ctor-body body #f))
800+
(ctor-def name Tname ctor-body sig body #f))
794801
(pattern-lambda (= (-$ (call name . sig) (|::| (call name . sig) _t)) body)
795-
(ctor-def name Tname params bounds sig ctor-body body #f))
802+
(ctor-def name Tname ctor-body sig body #f))
796803
;; definitions with `where`
797804
(pattern-lambda (function (where (-$ (call name . sig) (|::| (call name . sig) _t)) . wheres) body)
798-
(ctor-def name Tname params bounds sig ctor-body body wheres))
805+
(ctor-def name Tname ctor-body sig body wheres))
799806
(pattern-lambda (= (where (-$ (call name . sig) (|::| (call name . sig) _t)) . wheres) body)
800-
(ctor-def name Tname params bounds sig ctor-body body wheres)))
807+
(ctor-def name Tname ctor-body sig body wheres)))
801808

802809
;; flatten `where`s first
803810
(pattern-replace
@@ -853,7 +860,7 @@
853860
(block
854861
(global ,name)
855862
,@(map (lambda (c)
856-
(rewrite-ctor c name params bounds field-names field-types))
863+
(rewrite-ctor c name params field-names field-types))
857864
defs2)))
858865
;; "outer" constructors
859866
,@(if (and (null? defs)

test/core.jl

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6988,3 +6988,39 @@ end
69886988
# just constant folded by (future) over-eager compiler optimizations
69896989
@test isa(Core.eval(@__MODULE__, :(Bar31062(()))), Bar31062)
69906990
@test precompile(identity, (Foo31062,))
6991+
6992+
ftype_eval = Ref(0)
6993+
FieldTypeA = String
6994+
FieldTypeE = UInt32
6995+
struct FieldConvert{FieldTypeA, S}
6996+
a::FieldTypeA
6997+
b::(ftype_eval[] += 1; Vector{FieldTypeA})
6998+
c
6999+
d::Any
7000+
e::FieldTypeE
7001+
FieldConvert(a::S, b, c, d, e) where {S} = new{FieldTypeA, S}(a, b, c, d, e)
7002+
end
7003+
@test ftype_eval[] == 1
7004+
FieldTypeA = UInt64
7005+
FieldTypeE = String
7006+
let fc = FieldConvert(1.0, [2.0], 0x3, 0x4, 0x5)
7007+
@test fc.a === UInt64(1)
7008+
@test fc.b isa Vector{UInt64}
7009+
@test fc.c === 0x3
7010+
@test fc.d === 0x4
7011+
@test fc.e === UInt32(0x5)
7012+
end
7013+
@test ftype_eval[] == 1
7014+
let code = code_lowered(FieldConvert)[1].code
7015+
@test code[1] == Expr(:call, GlobalRef(Core, :apply_type), GlobalRef(@__MODULE__, :FieldConvert), GlobalRef(@__MODULE__, :FieldTypeA), Expr(:static_parameter, 1))
7016+
@test code[2] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 1)
7017+
@test code[3] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(2), Core.SlotNumber(2))
7018+
@test code[4] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 2)
7019+
@test code[5] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(4), Core.SlotNumber(3))
7020+
@test code[6] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 4)
7021+
@test code[7] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(6), Core.SlotNumber(5))
7022+
@test code[8] == Expr(:call, GlobalRef(Core, :fieldtype), Core.SSAValue(1), 5)
7023+
@test code[9] == Expr(:call, GlobalRef(Base, :convert), Core.SSAValue(8), Core.SlotNumber(6))
7024+
@test code[10] == Expr(:new, Core.SSAValue(1), Core.SSAValue(3), Core.SSAValue(5), Core.SlotNumber(4), Core.SSAValue(7), Core.SSAValue(9))
7025+
@test code[11] == Expr(:return, Core.SSAValue(10))
7026+
end

test/syntax.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,3 +1861,6 @@ let
18611861
a32325(x) = a32325()
18621862
end
18631863
@test a32325(0) === a32325()
1864+
1865+
@test Meta.lower(Main, :(struct A; A() = new{Int}(); end)) == Expr(:error, "too many type parameters specified in \"new{...}\"")
1866+
@test Meta.lower(Main, :(struct A{T, S}; A() = new{Int}(); end)) == Expr(:error, "too few type parameters specified in \"new{...}\"")

0 commit comments

Comments
 (0)